未验证 提交 0246be6c 编写于 作者: C Chris Sienkiewicz 提交者: GitHub

Enable global editor configs: (#43889)

* Enable global editor configs:
- Allow an editorconfig to be a global config via "is_global = true"
- Add a filter builder that reads a set of analyzer configs, filters out the global ones and returns a single merged global config
- Only match global sections based on full file path
- Issue a warning when two global configs set the same key, and remove the duplicate key
- Allow multiple global configs or an editorconfig and global config to exist in the same dir
- Add tests
上级 632020ae
......@@ -12234,6 +12234,80 @@ public ValueTuple(T1 item1)
Assert.Empty(output);
CleanupAllGeneratedFiles(srcFile.Path);
}
[Fact]
public void GlobalAnalyzerConfigsAllowedInSameDir()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("test.cs").WriteAllText(@"
class C
{
int _f;
}");
var configText = @"
is_global = true
";
var analyzerConfig1 = dir.CreateFile("analyzerconfig1").WriteAllText(configText);
var analyzerConfig2 = dir.CreateFile("analyzerconfig2").WriteAllText(configText);
var cmd = CreateCSharpCompiler(null, dir.Path, new[] {
"/nologo",
"/t:library",
"/preferreduilang:en",
"/analyzerconfig:" + analyzerConfig1.Path,
"/analyzerconfig:" + analyzerConfig2.Path,
src.Path
});
var outWriter = new StringWriter(CultureInfo.InvariantCulture);
var exitCode = cmd.Run(outWriter);
Assert.Equal(0, exitCode);
}
[Fact]
public void GlobalAnalyzerConfigMultipleSetKeys()
{
var dir = Temp.CreateDirectory();
var src = dir.CreateFile("temp.cs").WriteAllText(@"
class C
{
}");
var analyzerConfigFile = dir.CreateFile(".globalconfig");
var analyzerConfig = analyzerConfigFile.WriteAllText(@"
is_global = true
option1 = abc");
var analyzerConfigFile2 = dir.CreateFile(".globalconfig2");
var analyzerConfig2 = analyzerConfigFile2.WriteAllText(@"
is_global = true
option1 = def");
var output = VerifyOutput(dir, src, additionalFlags: new[] { "/analyzerconfig:" + analyzerConfig.Path + "," + analyzerConfig2.Path }, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false);
// warning MultipleGlobalAnalyzerKeys: Multiple global analyzer config files set the same key 'option1' in section 'Global Section'. It has been unset. Key was set by the following files: ...
Assert.Contains("MultipleGlobalAnalyzerKeys:", output, StringComparison.Ordinal);
Assert.Contains("'option1'", output, StringComparison.Ordinal);
Assert.Contains("'Global Section'", output, StringComparison.Ordinal);
analyzerConfig = analyzerConfigFile.WriteAllText(@"
is_global = true
[file.cs]
option1 = abc");
analyzerConfig2 = analyzerConfigFile2.WriteAllText(@"
is_global = true
[file.cs]
option1 = def");
output = VerifyOutput(dir, src, additionalFlags: new[] { "/analyzerconfig:" + analyzerConfig.Path + "," + analyzerConfig2.Path }, expectedWarningCount: 1, includeCurrentAssemblyAsAnalyzerReference: false);
// warning MultipleGlobalAnalyzerKeys: Multiple global analyzer config files set the same key 'option1' in section 'file.cs'. It has been unset. Key was set by the following files: ...
Assert.Contains("MultipleGlobalAnalyzerKeys:", output, StringComparison.Ordinal);
Assert.Contains("'option1'", output, StringComparison.Ordinal);
Assert.Contains("'file.cs'", output, StringComparison.Ordinal);
}
}
[DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
......
......@@ -1471,5 +1471,557 @@ public void TreesShareOptionsInstances()
}
#endregion
#region Processing of Global configs
[Fact]
public void IsReportedAsGlobal()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true ", "/.editorconfig"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out _);
Assert.Empty(configs);
Assert.NotNull(globalConfig);
configs.Free();
}
[Fact]
public void IsNotGlobalIfInSection()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
[*.cs]
is_global = true ", "/.editorconfig"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out _);
Assert.Single(configs);
Assert.Null(globalConfig);
configs.Free();
}
[Fact]
public void FilterReturnsSingleGlobalConfig()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1", "/.globalconfig1"));
configs.Add(Parse(@"option2 = value2", "/.editorconfig1"));
configs.Add(Parse(@"option3 = value3", "/.editorconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Equal(2, configs.Count);
Assert.NotNull(globalConfig);
Assert.Equal("value1", globalConfig.GlobalSection.Properties["option1"]);
configs.Free();
}
[Fact]
public void FilterReturnsSingleCombinedGlobalConfig()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
option2 = value2", "/.globalconfig2"));
configs.Add(Parse(@"option3 = value3", "/.editorconfig1"));
configs.Add(Parse(@"option4 = value4", "/.editorconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Equal(2, configs.Count);
Assert.NotNull(globalConfig);
Assert.Equal("value1", globalConfig.GlobalSection.Properties["option1"]);
Assert.Equal("value2", globalConfig.GlobalSection.Properties["option2"]);
configs.Free();
}
[Fact]
public void FilterCombinesSections()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1
[c:/path/to/file1.cs]
option1 = value1
[c:/path/to/file2.cs]
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
option2 = value2
[c:/path/to/file1.cs]
option2 = value2
[c:/path/to/file3.cs]
option1 = value1",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Empty(configs);
Assert.NotNull(globalConfig);
Assert.Equal("value1", globalConfig.GlobalSection.Properties["option1"]);
Assert.Equal("value2", globalConfig.GlobalSection.Properties["option2"]);
var file1Section = globalConfig.NamedSections[0];
var file2Section = globalConfig.NamedSections[1];
var file3Section = globalConfig.NamedSections[2];
Assert.Equal(@"c:/path/to/file1.cs", file1Section.Name);
Assert.Equal(2, file1Section.Properties.Count);
Assert.Equal("value1", file1Section.Properties["option1"]);
Assert.Equal("value2", file1Section.Properties["option2"]);
Assert.Equal(@"c:/path/to/file2.cs", file2Section.Name);
Assert.Equal(1, file2Section.Properties.Count);
Assert.Equal("value1", file2Section.Properties["option1"]);
Assert.Equal(@"c:/path/to/file3.cs", file3Section.Name);
Assert.Equal(1, file3Section.Properties.Count);
Assert.Equal("value1", file3Section.Properties["option1"]);
configs.Free();
}
[Fact]
public void DuplicateOptionsInGlobalConfigsAreUnset()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
option1 = value2", "/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify(
Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "Global Section", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
);
}
[Fact]
public void DuplicateOptionsInGlobalConfigsSectionsAreUnset()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify(
Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "c:/path/to/file1.cs", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
);
}
[Fact]
public void DuplicateGlobalOptionsInNonGlobalConfigsAreKept()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1", "/.globalconfig1"));
configs.Add(Parse(@"
option1 = value2", "/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
}
[Fact]
public void DuplicateSectionOptionsInNonGlobalConfigsAreKept()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"
[c:/path/to/file1.cs]
option1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
}
[Fact]
public void GlobalConfigsPropertiesAreGlobal()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1
", "/.globalconfig1"));
var options = GetAnalyzerConfigOptions(
new[] { "/file1.cs", "/path/to/file1.cs", "c:/path/to/file1.cs", "/file1.vb" },
configs);
configs.Free();
VerifyAnalyzerOptions(
new[]
{
new[] { ("option1", "value1") },
new[] { ("option1", "value1") },
new[] { ("option1", "value1") },
new[] { ("option1", "value1") }
},
options);
}
[Fact]
public void GlobalConfigsSectionsMustBeFullPath()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value1
[*.cs]
option2 = value2
[.*/path/*.cs]
option3 = value3
[c:/.*/*.cs]
option4 = value4
", "/.globalconfig1"));
var options = GetAnalyzerConfigOptions(
new[] { "/file1.cs", "/path/to/file1.cs", "c:/path/to/file1.cs", "/file1.vb" },
configs);
configs.Free();
VerifyAnalyzerOptions(
new[]
{
new (string, string)[] { },
new (string, string)[] { },
new (string, string)[]
{
("option1", "value1")
},
new (string, string)[] { }
},
options);
}
[Fact]
public void GlobalConfigsSectionsAreOverriddenByNonGlobal()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = global
[c:/path/to/file1.cs]
option2 = global
option3 = global
", "/.globalconfig1"));
configs.Add(Parse(@"
[*.cs]
option2 = config1
", "/.editorconfig"));
configs.Add(Parse(@"
[*.cs]
option3 = config2
", "/path/.editorconfig"));
configs.Add(Parse(@"
[*.cs]
option2 = config3
", "/path/to/.editorconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "/path/to/file1.cs", "/path/file1.cs", "/file1.cs" },
configs);
configs.Free();
VerifyAnalyzerOptions(
new[]
{
new []
{
("option1", "global"),
("option2", "config3"), // overridden by config3
("option3", "config2") // overridden by config2
},
new []
{
("option1", "global"),
("option2", "config1"),
("option3", "config2")
},
new []
{
("option1", "global"),
("option2", "config1")
}
},
options);
}
[Fact]
public void GlobalConfigSectionsAreCaseSensitive()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
[c:/pAth/To/fiLe1.cs]
option1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify();
Assert.Equal(2, globalConfig.NamedSections.Length);
configs.Free();
}
[Fact]
public void GlobalConfigSectionsPopertiesAreNotCaseSensitive()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
opTioN1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify(
Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "c:/path/to/file1.cs", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
);
configs.Free();
}
[Fact]
public void GlobalConfigPropertiesAreNotCaseSensitive()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
option1 = value1
", "/.globalconfig1"));
configs.Add(Parse(@"is_global = true
opTioN1 = value2",
"/.globalconfig2"));
var globalConfig = AnalyzerConfigSet.MergeGlobalConfigs(configs, out var diagnostics);
diagnostics.Verify(
Diagnostic("MultipleGlobalAnalyzerKeys").WithArguments("option1", "Global Section", "/.globalconfig1, /.globalconfig2").WithLocation(1, 1)
);
configs.Free();
}
[Fact]
public void GlobalConfigSectionPathsMustBeNormalized()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"is_global = true
[c:/path/to/file1.cs]
option1 = value1
[c:\path\to\file2.cs]
option1 = value1
", "/.globalconfig1"));
var options = GetAnalyzerConfigOptions(
new[] { "c:/path/to/file1.cs", "c:/path/to/file2.cs" },
configs);
configs.Free();
VerifyAnalyzerOptions(
new[]
{
new []
{
("option1", "value1")
},
new (string, string) [] { }
},
options);
}
[Fact]
public void GlobalConfigCanSetSeverity()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
dotnet_diagnostic.cs000.severity = none
dotnet_diagnostic.cs001.severity = error
", "/.editorconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "/test.cs" },
configs);
configs.Free();
Assert.Equal(CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress),
("cs001", ReportDiagnostic.Error)),
options[0].TreeOptions);
}
[Fact]
public void GlobalConfigCanSetSeverityInSection()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
[c:/path/to/file.cs]
dotnet_diagnostic.cs000.severity = error
", "/.editorconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "/test.cs", "c:/path/to/file.cs" },
configs);
configs.Free();
Assert.Equal(new[] {
SyntaxTree.EmptyDiagnosticOptions,
CreateImmutableDictionary(("cs000", ReportDiagnostic.Error))
}, options.Select(o => o.TreeOptions).ToArray());
}
[Fact]
public void GlobalConfigInvalidSeverity()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
dotnet_diagnostic.cs000.severity = foo
[c:/path/to/file.cs]
dotnet_diagnostic.cs001.severity = bar
", "/.editorconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "/test.cs", "c:/path/to/file.cs" },
configs);
configs.Free();
options[0].Diagnostics.Verify(
Diagnostic("InvalidSeverityInAnalyzerConfig").WithArguments("cs000", "foo", "<Global Config>").WithLocation(1, 1)
);
options[1].Diagnostics.Verify(
Diagnostic("InvalidSeverityInAnalyzerConfig").WithArguments("cs000", "foo", "<Global Config>").WithLocation(1, 1),
Diagnostic("InvalidSeverityInAnalyzerConfig").WithArguments("cs001", "bar", "<Global Config>").WithLocation(1, 1)
);
}
[Fact]
public void GlobalConfigCanSeverityInSectionOverridesGlobal()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
dotnet_diagnostic.cs000.severity = none
[c:/path/to/file.cs]
dotnet_diagnostic.cs000.severity = error
", "/.editorconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "c:/path/to/file.cs" },
configs);
configs.Free();
Assert.Equal(
CreateImmutableDictionary(("cs000", ReportDiagnostic.Error)),
options[0].TreeOptions);
}
[Fact]
public void GlobalConfigSeverityIsOverridenByEditorConfig()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
dotnet_diagnostic.cs000.severity = error
", "/.globalconfig"));
configs.Add(Parse(@"
[*.cs]
dotnet_diagnostic.cs000.severity = none
", "/.editorconfig"));
configs.Add(Parse(@"
[*.cs]
dotnet_diagnostic.cs000.severity = warning
", "/path/.editorconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "/test.cs", "/path/file.cs" },
configs);
configs.Free();
Assert.Equal(new[] {
CreateImmutableDictionary(("cs000", ReportDiagnostic.Suppress)),
CreateImmutableDictionary(("cs000", ReportDiagnostic.Warn))
}, options.Select(o => o.TreeOptions).ToArray());
}
[Fact]
public void GlobalKeyIsNotSkippedIfInSection()
{
var configs = ArrayBuilder<AnalyzerConfig>.GetInstance();
configs.Add(Parse(@"
is_global = true
[/path/to/file.cs]
is_global = true
", "/.globalconfig"));
var options = GetAnalyzerConfigOptions(
new[] { "/file.cs", "/path/to/file.cs" },
configs);
configs.Free();
VerifyAnalyzerOptions(
new[]
{
new (string,string)[] { },
new[] { ("is_global", "true") }
},
options);
}
#endregion
}
}
......@@ -690,4 +690,10 @@
<data name="Single_type_per_generator_0" xml:space="preserve">
<value>Only a single {0} can be registered per generator.</value>
</data>
<data name="WRN_MultipleGlobalAnalyzerKeys" xml:space="preserve">
<value>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</value>
</data>
<data name="WRN_MultipleGlobalAnalyzerKeys_Title" xml:space="preserve">
<value>Multiple global analyzer config files set the same key. It has been unset.</value>
</data>
</root>
\ No newline at end of file
......@@ -25,6 +25,11 @@ public sealed partial class AnalyzerConfig
// Matches EditorConfig property such as "indent_style = space", see http://editorconfig.org for details
private static readonly Regex s_propertyMatcher = new Regex(@"^\s*([\w\.\-_]+)\s*[=:]\s*(.*?)\s*([#;].*)?$", RegexOptions.Compiled);
/// <summary>
/// Key that indicates if this config is a global config
/// </summary>
internal const string GlobalKey = "is_global";
/// <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
......@@ -80,6 +85,11 @@ public sealed partial class AnalyzerConfig
/// </summary>
internal bool IsRoot => GlobalSection.Properties.TryGetValue("root", out string val) && val == "true";
/// <summary>
/// Gets whether this editorconfig is a global editorconfig.
/// </summary>
internal bool IsGlobal => GlobalSection.Properties.ContainsKey(GlobalKey);
private AnalyzerConfig(
Section globalSection,
ImmutableArray<Section> namedSections,
......@@ -222,6 +232,12 @@ internal sealed class Section
/// </summary>
public static StringComparison NameComparer { get; } = StringComparison.Ordinal;
/// <summary>
/// Used to compare <see cref="Name"/>s of sections. Specified by editorconfig to
/// be a case-sensitive comparison.
/// </summary>
public static IEqualityComparer<string> NameEqualityComparer { get; } = StringComparer.Ordinal;
/// <summary>
/// Used to compare keys in <see cref="Properties"/>. The editorconfig spec defines property
/// keys as being compared case-insensitively according to Unicode lower-case rules.
......
......@@ -30,6 +30,8 @@ public sealed class AnalyzerConfigSet
/// </summary>
private readonly ImmutableArray<AnalyzerConfig> _analyzerConfigs;
private readonly GlobalAnalyzerConfig? _globalConfig;
/// <summary>
/// <see cref="SectionNameMatcher"/>s for each section. The entries in the outer array correspond to entries in <see cref="_analyzerConfigs"/>, and each inner array
/// corresponds to each <see cref="AnalyzerConfig.NamedSections"/>.
......@@ -93,18 +95,34 @@ public bool Equals([AllowNull] List<Section> x, [AllowNull] List<Section> y)
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
private readonly static DiagnosticDescriptor MultipleGlobalAnalyzerKeysDescriptor
= new DiagnosticDescriptor(
"MultipleGlobalAnalyzerKeys",
CodeAnalysisResources.WRN_MultipleGlobalAnalyzerKeys_Title,
CodeAnalysisResources.WRN_MultipleGlobalAnalyzerKeys,
"AnalyzerConfig",
DiagnosticSeverity.Warning,
isEnabledByDefault: true);
public static AnalyzerConfigSet Create<TList>(TList analyzerConfigs) where TList : IReadOnlyCollection<AnalyzerConfig>
{
return Create(analyzerConfigs, out _);
}
public static AnalyzerConfigSet Create<TList>(TList analyzerConfigs, out ImmutableArray<Diagnostic> diagnostics) where TList : IReadOnlyCollection<AnalyzerConfig>
{
var sortedAnalyzerConfigs = ArrayBuilder<AnalyzerConfig>.GetInstance(analyzerConfigs.Count);
sortedAnalyzerConfigs.AddRange(analyzerConfigs);
sortedAnalyzerConfigs.Sort(AnalyzerConfig.DirectoryLengthComparer);
return new AnalyzerConfigSet(sortedAnalyzerConfigs.ToImmutableAndFree());
var globalConfig = MergeGlobalConfigs(sortedAnalyzerConfigs, out diagnostics);
return new AnalyzerConfigSet(sortedAnalyzerConfigs.ToImmutableAndFree(), globalConfig);
}
private AnalyzerConfigSet(ImmutableArray<AnalyzerConfig> analyzerConfigs)
private AnalyzerConfigSet(ImmutableArray<AnalyzerConfig> analyzerConfigs, GlobalAnalyzerConfig? globalConfig)
{
_analyzerConfigs = analyzerConfigs;
_globalConfig = globalConfig;
var allMatchers = ArrayBuilder<ImmutableArray<SectionNameMatcher?>>.GetInstance(_analyzerConfigs.Length);
......@@ -146,6 +164,18 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)
var normalizedPath = PathUtilities.NormalizeWithForwardSlash(sourcePath);
// If we have a global config, add any sections that match the full path
if (_globalConfig is object)
{
foreach (var section in _globalConfig.NamedSections)
{
if (normalizedPath.Equals(section.Name, Section.NameComparer))
{
sectionKey.Add(section);
}
}
}
// The editorconfig paths are sorted from shortest to longest, so matches
// are resolved from most nested to least nested, where last setting wins
for (int analyzerConfigIndex = 0; analyzerConfigIndex < _analyzerConfigs.Length; analyzerConfigIndex++)
......@@ -192,6 +222,36 @@ public AnalyzerConfigOptionsResult GetOptionsForSourcePath(string sourcePath)
var diagnosticBuilder = ArrayBuilder<Diagnostic>.GetInstance();
int sectionKeyIndex = 0;
if (_globalConfig is object)
{
addOptions(_globalConfig.GlobalSection,
treeOptionsBuilder,
analyzerOptionsBuilder,
diagnosticBuilder,
GlobalAnalyzerConfigBuilder.GlobalConfigPath,
_diagnosticIdCache);
foreach (var configSection in _globalConfig.NamedSections)
{
if (sectionKey.Count > 0 && configSection == sectionKey[sectionKeyIndex])
{
addOptions(
sectionKey[sectionKeyIndex],
treeOptionsBuilder,
analyzerOptionsBuilder,
diagnosticBuilder,
GlobalAnalyzerConfigBuilder.GlobalConfigPath,
_diagnosticIdCache);
sectionKeyIndex++;
if (sectionKeyIndex == sectionKey.Count)
{
break;
}
}
}
}
for (int analyzerConfigIndex = 0;
analyzerConfigIndex < _analyzerConfigs.Length && sectionKeyIndex < sectionKey.Count;
analyzerConfigIndex++)
......@@ -349,5 +409,180 @@ internal static bool TryParseSeverity(string value, out ReportDiagnostic severit
severity = default;
return false;
}
/// <summary>
/// Merge any partial global configs into a single global config, and remove the partial configs
/// </summary>
/// <param name="analyzerConfigs">An <see cref="ArrayBuilder{T}"/> of <see cref="AnalyzerConfig"/> containing a mix of regular and unmerged partial global configs</param>
/// <param name="diagnostics">Diagnostics produced during merge will be added to this bag</param>
/// <returns>A <see cref="GlobalAnalyzerConfig" /> that contains the merged partial configs, or <c>null</c> if there were no partial configs</returns>
internal static GlobalAnalyzerConfig? MergeGlobalConfigs(ArrayBuilder<AnalyzerConfig> analyzerConfigs, out ImmutableArray<Diagnostic> diagnostics)
{
GlobalAnalyzerConfigBuilder globalAnalyzerConfigBuilder = new GlobalAnalyzerConfigBuilder();
for (int i = 0; i < analyzerConfigs.Count; i++)
{
if (analyzerConfigs[i].IsGlobal)
{
globalAnalyzerConfigBuilder.MergeIntoGlobalConfig(analyzerConfigs[i]);
analyzerConfigs.RemoveAt(i);
i--;
}
}
DiagnosticBag diagnosticBag = DiagnosticBag.GetInstance();
var globalConfig = globalAnalyzerConfigBuilder.Build(diagnosticBag);
diagnostics = diagnosticBag.ToReadOnlyAndFree();
return globalConfig;
}
/// <summary>
/// Builds a global analyzer config from a series of partial configs
/// </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;
internal const string GlobalConfigPath = "<Global Config>";
internal const string GlobalSectionName = "Global Section";
internal void MergeIntoGlobalConfig(AnalyzerConfig config)
{
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);
}
MergeSection(config.PathToFile, config.GlobalSection, isGlobalSection: true);
foreach (var section in config.NamedSections)
{
MergeSection(config.PathToFile, section, isGlobalSection: false);
}
}
internal GlobalAnalyzerConfig? Build(DiagnosticBag diagnostics)
{
if (_values is null || _duplicates is null)
{
return null;
}
// issue diagnostics for any duplicate keys
foreach ((var section, var keys) in _duplicates)
{
bool isGlobalSection = string.IsNullOrWhiteSpace(section);
string sectionName = isGlobalSection ? GlobalSectionName : section;
foreach ((var keyName, var configPaths) in keys)
{
diagnostics.Add(Diagnostic.Create(
MultipleGlobalAnalyzerKeysDescriptor,
Location.None,
keyName,
sectionName,
string.Join(", ", configPaths)));
}
}
_duplicates = null;
// gather the global and named sections
Section globalSection = GetSection(string.Empty);
_values.Remove(string.Empty);
ArrayBuilder<Section> namedSectionBuilder = new ArrayBuilder<Section>(_values.Count);
foreach (var sectionName in _values.Keys.Order())
{
namedSectionBuilder.Add(GetSection(sectionName));
}
// create the global config
GlobalAnalyzerConfig globalConfig = new GlobalAnalyzerConfig(globalSection, namedSectionBuilder.ToImmutableAndFree());
_values = null;
return globalConfig;
}
private Section GetSection(string sectionName)
{
Debug.Assert(_values is object);
var dict = _values[sectionName];
var result = dict.ToImmutableDictionary(d => d.Key, d => d.Value.value, Section.PropertiesKeyComparer);
return new Section(sectionName, result);
}
private void MergeSection(string configPath, Section section, 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);
_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))
{
continue;
}
bool keyInSection = sectionDict.ContainsKey(key);
bool keyDuplicated = duplicateDict?.ContainsKey(key) ?? false;
// if this key is neither already present, or already duplicate, we can add it
if (!keyInSection && !keyDuplicated)
{
sectionDict.Add(key, (value, configPath));
}
else
{
if (duplicateDict is null)
{
duplicateDict = ImmutableDictionary.CreateBuilder<string, 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;
// 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);
}
}
}
}
}
/// <summary>
/// Represents a combined global analyzer config.
/// </summary>
/// <remarks>
/// We parse all <see cref="AnalyzerConfig"/>s as individual files, according to the editorconfig spec.
///
/// However, when viewing the configs as an <see cref="AnalyzerConfigSet"/> if multiple files have the
/// <c>is_global</c> property set to <c>true</c> we combine those files and treat them as a single
/// 'logical' global config file. This type represents that combined file.
/// </remarks>
internal sealed class GlobalAnalyzerConfig
{
internal AnalyzerConfig.Section GlobalSection { get; }
internal ImmutableArray<AnalyzerConfig.Section> NamedSections { get; }
public GlobalAnalyzerConfig(AnalyzerConfig.Section globalSection, ImmutableArray<AnalyzerConfig.Section> namedSections)
{
GlobalSection = globalSection;
NamedSections = namedSections;
}
}
}
}
......@@ -299,18 +299,20 @@ internal SourceText TryReadFileContent(CommandLineSourceFile file, IList<Diagnos
}
var directory = Path.GetDirectoryName(normalizedPath) ?? normalizedPath;
var editorConfig = AnalyzerConfig.Parse(fileContent, normalizedPath);
if (processedDirs.Contains(directory))
if (!editorConfig.IsGlobal)
{
diagnostics.Add(Diagnostic.Create(
MessageProvider,
MessageProvider.ERR_MultipleAnalyzerConfigsInSameDir,
directory));
break;
if (processedDirs.Contains(directory))
{
diagnostics.Add(Diagnostic.Create(
MessageProvider,
MessageProvider.ERR_MultipleAnalyzerConfigsInSameDir,
directory));
break;
}
processedDirs.Add(directory);
}
processedDirs.Add(directory);
var editorConfig = AnalyzerConfig.Parse(fileContent, normalizedPath);
configs.Add(editorConfig);
}
......@@ -323,7 +325,8 @@ internal SourceText TryReadFileContent(CommandLineSourceFile file, IList<Diagnos
return false;
}
analyzerConfigSet = AnalyzerConfigSet.Create(configs);
analyzerConfigSet = AnalyzerConfigSet.Create(configs, out var setDiagnostics);
diagnostics.AddRange(setDiagnostics);
return true;
}
......
......@@ -19,6 +19,7 @@ Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation.OperatorKind.get -
Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation.Value.get -> Microsoft.CodeAnalysis.IOperation
Microsoft.CodeAnalysis.Operations.ITypePatternOperation
Microsoft.CodeAnalysis.Operations.ITypePatternOperation.MatchedType.get -> Microsoft.CodeAnalysis.ITypeSymbol
static Microsoft.CodeAnalysis.AnalyzerConfigSet.Create<TList>(TList analyzerConfigs, out System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.Diagnostic> diagnostics) -> Microsoft.CodeAnalysis.AnalyzerConfigSet
virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitBinaryPattern(Microsoft.CodeAnalysis.Operations.IBinaryPatternOperation operation) -> void
virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitNegatedPattern(Microsoft.CodeAnalysis.Operations.INegatedPatternOperation operation) -> void
virtual Microsoft.CodeAnalysis.Operations.OperationVisitor.VisitRelationalPattern(Microsoft.CodeAnalysis.Operations.IRelationalPatternOperation operation) -> void
......
......@@ -554,6 +554,16 @@
<target state="translated">Neplatná závažnost v konfiguračním souboru analyzátoru</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">U identity WindowsRuntime se nedá změnit cíl.</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Ungültiger Schweregrad in der Konfigurationsdatei des Analysetools.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">WindowsRuntime-Identität darf nicht anzielbar sein</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Gravedad no válida en el archivo de configuración del analizador.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">La identidad WindowsRuntime no puede ser redestinable</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Niveau de gravité non valide dans le fichier config de l'analyseur.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">L'identité WindowsRuntime ne peut pas être reciblable</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Gravità non valida nel file di configurazione dell'analizzatore.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">Non è possibile ridefinire la destinazione dell'identità WindowsRuntime</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">アナライザー構成ファイルの重大度が無効です。</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">WindowsRuntime の ID を再ターゲット可能にすることはできません</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">분석기 구성 파일의 심각도가 잘못되었습니다.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">WindowsRuntime ID는 대상으로 다시 지정할 수 없습니다.</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Nieprawidłowa ważność w pliku konfiguracji analizatora.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">Tożsamość WindowsRuntime nie może być elementem, który można ponownie ustawić jako cel</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Gravidade inválida no arquivo de configuração do analisador.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">Identidade de WindowsRuntime não pode ser redirecionável</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Недопустимая серьезность в файле конфигурации анализатора.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">Идентификатор WindowsRuntime не может быть перенацеливаемым</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">Çözümleyici yapılandırma dosyasında geçersiz önem derecesi.</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">WindowsRuntime kimliği yeniden hedeflendirilebilir olamaz</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">分析器配置文件中的严重性无效。</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">WindowsRuntime 标识不可重定目标</target>
......
......@@ -554,6 +554,16 @@
<target state="translated">分析器組態檔中的嚴重性無效。</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys">
<source>Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</source>
<target state="new">Multiple global analyzer config files set the same key '{0}' in section '{1}'. It has been unset. Key was set by the following files: '{2}'</target>
<note />
</trans-unit>
<trans-unit id="WRN_MultipleGlobalAnalyzerKeys_Title">
<source>Multiple global analyzer config files set the same key. It has been unset.</source>
<target state="new">Multiple global analyzer config files set the same key. It has been unset.</target>
<note />
</trans-unit>
<trans-unit id="WinRTIdentityCantBeRetargetable">
<source>WindowsRuntime identity can't be retargetable</source>
<target state="translated">無法重新指定 WindowsRuntime 身分識別目標</target>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册