Skip to content

Commit 48e81c6

Browse files
authored
Update configuration parsing logic (#10361)
1 parent 2a8b16d commit 48e81c6

File tree

6 files changed

+127
-52
lines changed

6 files changed

+127
-52
lines changed

documentation/specs/BuildCheck/BuildCheck.md

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,11 @@ For the `.editorconfig` file configuration, following will apply:
163163

164164
```ini
165165
[*.csproj]
166-
build_check.BC0101.Severity=warning
166+
build_check.BC0101.severity=warning
167167

168-
build_check.COND0543.Severity=none
169-
build_check.COND0543.EvaluationAnalysisScope=AnalyzedProjectOnly
170-
build_check.COND0543.CustomSwitch=QWERTY
168+
build_check.COND0543.severity=none
169+
build_check.COND0543.scope=project
170+
build_check.COND0543.custom_switch=QWERTY
171171
```
172172

173173
### User Configurable Options
@@ -176,15 +176,17 @@ Initial version of BuildCheck plans a limited set of options configurable by use
176176

177177
**NOTE:** The actual naming of the configuration options is yet to be determined.
178178

179-
#### Severity
179+
#### Severity levels
180180

181181
Option `Severity` with following values will be available:
182182

183-
* `Default`
184-
* `None`
185-
* `Suggestion`
186-
* `Warning`
187-
* `Error`
183+
| Severity | EditorConfig option |
184+
| ------------- | ------------- |
185+
| Default | `default` |
186+
| None | `none` |
187+
| Suggestion | `suggestion` |
188+
| Warning | `warning` |
189+
| Error | `error` |
188190

189191
Severity levels are in line with [roslyn analyzers severity levels](https://learn.microsoft.com/en-us/visualstudio/code-quality/use-roslyn-analyzers). `Default` severity in `.editorconfig` will lead to using build-in severity from the analyzer (so this can be used for clearing custom severity setting from higher level `.editorconfig` file). `Default` severity in the build-in code has same effect as if the code doesn't specify severity at all - an infrastruture default of `None` is considered.
190192

@@ -194,20 +196,36 @@ Each rule has a severity, even if multiple rules are defined in a single analyze
194196

195197
If all the rules from a single analyzer have severity `None` - analyzer won't be given any data for such configured part of the build (specific project or a whole build). If analyzer have some rules enabled and some disabled - it will be still fed with data, but the reports will be post-filtered.
196198

199+
#### Configuring severity level
200+
201+
```ini
202+
[*.csproj]
203+
build_check.BC0101.severity=warning
204+
```
205+
197206
#### Scope of Analysis
198207

199208
Option `EvaluationAnalysisScope` with following possible options will be available:
200-
* `ProjectOnly` - Only the data from currently analyzed project will be sent to the analyzer. Imports will be discarded.
201-
* `ProjectWithImportsFromCurrentWorkTree` - Only the data from currently analyzed project and imports from files under the entry project or solution will be sent to the analyzer. Other imports will be discarded.
202-
* `ProjectWithImportsWithoutSdks` - Imports from SDKs will not be sent to the analyzer. Other imports will be sent.
203-
* `ProjectWithAllImports` - All data will be sent to the analyzer.
209+
210+
| EvaluationAnalysisScope (Solution Explorer) | EditorConfig option | Behavior |
211+
| ------------- | ------------- | ------------- |
212+
| ProjectOnly | `project` | Only the data from currently analyzed project will be sent to the analyzer. Imports will be discarded. |
213+
| ProjectWithImportsFromCurrentWorkTree | `current_imports` | Only the data from currently analyzed project and imports from files under the entry project or solution will be sent to the analyzer. Other imports will be discarded. |
214+
| ProjectWithImportsWithoutSdks | `without_sdks` | Imports from SDKs will not be sent to the analyzer. Other imports will be sent. |
215+
| ProjectWithAllImports | `all` | All data will be sent to the analyzer. |
204216

205217
All rules of a single analyzer must have the `EvaluationAnalysisScope` configured to a same value. If any rule from the analyzer have the value configured differently - a warning will be issued during the build and analyzer will be deregistered.
206218

207219
Same rule can have `EvaluationAnalysisScope` configured to different values for different projects.
208220

209221
BuildCheck might not be able to guarantee to properly filter the data with this distinction for all [registration types](#RegisterActions) - in case an explicit value is attempted to be configured (either [from the analyzer code](#BuildAnalyzerConfiguration) or from `.editorconfig` file) for an analyzer that has a subscription to unfilterable data - a warning will be issued during the build and analyzer will be deregistered.
210222

223+
#### Configuring evalution scope
224+
225+
```ini
226+
[*.csproj]
227+
build_check.BC0101.scope=all
228+
```
211229

212230
## Analyzers and Rules Identification
213231

src/Build/BuildCheck/API/BuildAnalyzerConfiguration.cs

Lines changed: 60 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using Microsoft.Build.Experimental.BuildCheck.Infrastructure;
7+
using Microsoft.Build.Experimental.BuildCheck.Utilities;
78

89
namespace Microsoft.Build.Experimental.BuildCheck;
910

@@ -66,32 +67,78 @@ public bool? IsEnabled {
6667
/// </summary>
6768
/// <param name="configDictionary">The configuration dictionary containing the settings for the build analyzer. The configuration's keys are expected to be in lower case or the EqualityComparer to ignore case.</param>
6869
/// <returns>A new instance of <see cref="BuildAnalyzerConfiguration"/> with the specified settings.</returns>
69-
internal static BuildAnalyzerConfiguration Create(Dictionary<string, string>? configDictionary)
70+
internal static BuildAnalyzerConfiguration Create(Dictionary<string, string>? configDictionary) => new()
7071
{
71-
return new()
72+
EvaluationAnalysisScope = TryExtractEvaluationAnalysisScope(configDictionary),
73+
Severity = TryExtractSeverity(configDictionary),
74+
};
75+
76+
77+
private static EvaluationAnalysisScope? TryExtractEvaluationAnalysisScope(Dictionary<string, string>? config)
78+
{
79+
80+
if (!TryExtractValue(BuildCheckConstants.scopeConfigurationKey, config, out string? stringValue) || stringValue is null)
7281
{
73-
EvaluationAnalysisScope = TryExtractValue(nameof(EvaluationAnalysisScope), configDictionary, out EvaluationAnalysisScope evaluationAnalysisScope) ? evaluationAnalysisScope : null,
74-
Severity = TryExtractValue(nameof(Severity), configDictionary, out BuildAnalyzerResultSeverity severity) ? severity : null
75-
};
82+
return null;
83+
}
84+
85+
switch (stringValue)
86+
{
87+
case "project":
88+
return BuildCheck.EvaluationAnalysisScope.ProjectOnly;
89+
case "current_imports":
90+
return BuildCheck.EvaluationAnalysisScope.ProjectWithImportsFromCurrentWorkTree;
91+
case "without_sdks":
92+
return BuildCheck.EvaluationAnalysisScope.ProjectWithImportsWithoutSdks;
93+
case "all":
94+
return BuildCheck.EvaluationAnalysisScope.ProjectWithAllImports;
95+
default:
96+
ThrowIncorrectValueException(BuildCheckConstants.scopeConfigurationKey, stringValue);
97+
break;
98+
}
99+
100+
return null;
76101
}
77102

78-
private static bool TryExtractValue<T>(string key, Dictionary<string, string>? config, out T value) where T : struct, Enum
103+
private static BuildAnalyzerResultSeverity? TryExtractSeverity(Dictionary<string, string>? config)
79104
{
80-
value = default;
105+
if (!TryExtractValue(BuildCheckConstants.severityConfigurationKey, config, out string? stringValue) || stringValue is null)
106+
{
107+
return null;
108+
}
81109

82-
if (config == null || !config.TryGetValue(key.ToLower(), out var stringValue) || stringValue is null)
110+
switch (stringValue)
83111
{
84-
return false;
112+
case "none":
113+
return BuildAnalyzerResultSeverity.None;
114+
case "default":
115+
return BuildAnalyzerResultSeverity.Default;
116+
case "suggestion":
117+
return BuildAnalyzerResultSeverity.Suggestion;
118+
case "warning":
119+
return BuildAnalyzerResultSeverity.Warning;
120+
case "error":
121+
return BuildAnalyzerResultSeverity.Error;
122+
default:
123+
ThrowIncorrectValueException(BuildCheckConstants.severityConfigurationKey, stringValue);
124+
break;
85125
}
86126

87-
var isParsed = Enum.TryParse(stringValue, true, out value);
127+
return null;
128+
}
129+
130+
private static bool TryExtractValue(string key, Dictionary<string, string>? config, out string? stringValue)
131+
{
132+
stringValue = null;
88133

89-
if (!isParsed)
134+
if (config == null || !config.TryGetValue(key.ToLower(), out stringValue) || stringValue is null)
90135
{
91-
ThrowIncorrectValueException(key, stringValue);
136+
return false;
92137
}
93138

94-
return isParsed;
139+
stringValue = stringValue.ToLower();
140+
141+
return true;
95142
}
96143

97144
private static void ThrowIncorrectValueException(string key, string value)

src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using Microsoft.Build.Experimental.BuildCheck.Infrastructure.EditorConfig;
88
using Microsoft.Build.Experimental.BuildCheck;
99
using System.Collections.Concurrent;
10+
using Microsoft.Build.Experimental.BuildCheck.Utilities;
1011

1112
namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure;
1213

@@ -31,9 +32,9 @@ internal sealed class ConfigurationProvider
3132
/// </summary>
3233
private readonly ConcurrentDictionary<string, CustomConfigurationData> _customConfigurationData = new ConcurrentDictionary<string, CustomConfigurationData>(StringComparer.InvariantCultureIgnoreCase);
3334

34-
private readonly string[] _infrastructureConfigurationKeys = new string[] {
35-
nameof(BuildAnalyzerConfiguration.EvaluationAnalysisScope).ToLower(),
36-
nameof(BuildAnalyzerConfiguration.Severity).ToLower()
35+
private readonly string[] _infrastructureConfigurationKeys = {
36+
BuildCheckConstants.scopeConfigurationKey,
37+
BuildCheckConstants.severityConfigurationKey,
3738
};
3839

3940
/// <summary>

src/Build/BuildCheck/Utilities/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ namespace Microsoft.Build.Experimental.BuildCheck.Utilities;
1515
internal static class BuildCheckConstants
1616
{
1717
internal const string infraStatPrefix = "infrastructureStat_";
18+
19+
internal const string severityConfigurationKey = "severity";
20+
internal const string scopeConfigurationKey = "scope";
1821
}

src/BuildCheck.UnitTests/BuildAnalyzerConfiguration_Test.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,19 @@ public void CreateBuildAnalyzerConfiguration_SeverityAndEnabledOrder(string para
7575
}
7676

7777
[Theory]
78-
[InlineData("ProjectOnly", EvaluationAnalysisScope.ProjectOnly)]
79-
[InlineData("ProjectWithImportsFromCurrentWorkTree", EvaluationAnalysisScope.ProjectWithImportsFromCurrentWorkTree)]
80-
[InlineData("ProjectWithImportsWithoutSdks", EvaluationAnalysisScope.ProjectWithImportsWithoutSdks)]
81-
[InlineData("ProjectWithAllImports", EvaluationAnalysisScope.ProjectWithAllImports)]
82-
[InlineData("projectwithallimports", EvaluationAnalysisScope.ProjectWithAllImports)]
78+
[InlineData("project", EvaluationAnalysisScope.ProjectOnly)]
79+
[InlineData("PROJECT", EvaluationAnalysisScope.ProjectOnly)]
80+
[InlineData("current_imports", EvaluationAnalysisScope.ProjectWithImportsFromCurrentWorkTree)]
81+
[InlineData("CURRENT_IMPORTS", EvaluationAnalysisScope.ProjectWithImportsFromCurrentWorkTree)]
82+
[InlineData("without_sdks", EvaluationAnalysisScope.ProjectWithImportsWithoutSdks)]
83+
[InlineData("WITHOUT_SDKS", EvaluationAnalysisScope.ProjectWithImportsWithoutSdks)]
84+
[InlineData("all", EvaluationAnalysisScope.ProjectWithAllImports)]
85+
[InlineData("ALL", EvaluationAnalysisScope.ProjectWithAllImports)]
8386
public void CreateBuildAnalyzerConfiguration_EvaluationAnalysisScope(string parameter, EvaluationAnalysisScope? expected)
8487
{
8588
var config = new Dictionary<string, string>()
8689
{
87-
{ "evaluationanalysisscope" , parameter },
90+
{ "scope" , parameter },
8891
};
8992

9093
var buildConfig = BuildAnalyzerConfiguration.Create(config);
@@ -97,7 +100,7 @@ public void CreateBuildAnalyzerConfiguration_EvaluationAnalysisScope(string para
97100
}
98101

99102
[Theory]
100-
[InlineData("evaluationanalysisscope", "incorrec-value")]
103+
[InlineData("scope", "incorrec-value")]
101104
[InlineData("severity", "incorrec-value")]
102105
public void CreateBuildAnalyzerConfiguration_ExceptionOnInvalidInputValue(string key, string value)
103106
{
@@ -106,7 +109,8 @@ public void CreateBuildAnalyzerConfiguration_ExceptionOnInvalidInputValue(string
106109
{ key , value },
107110
};
108111

109-
var exception = Should.Throw<BuildCheckConfigurationException>(() => {
112+
var exception = Should.Throw<BuildCheckConfigurationException>(() =>
113+
{
110114
BuildAnalyzerConfiguration.Create(config);
111115
});
112116
exception.Message.ShouldContain($"Incorrect value provided in config for key {key}");

src/BuildCheck.UnitTests/ConfigurationProvider_Tests.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ public void GetRuleIdConfiguration_CustomConfigurationData()
7676
[*.csproj]
7777
build_check.rule_id.property1=value1
7878
build_check.rule_id.property2=value2
79-
build_check.rule_id.isEnabled2=true
79+
build_check.rule_id.is_enabled_2=true
80+
build_check.rule_id.scope=project
81+
build_check.rule_id.severity=default
8082
any_other_key1=any_other_value1
8183
any_other_key2=any_other_value2
8284
any_other_key3=any_other_value3
@@ -91,7 +93,7 @@ public void GetRuleIdConfiguration_CustomConfigurationData()
9193

9294
configs.ContainsKey("property1").ShouldBeTrue();
9395
configs.ContainsKey("property2").ShouldBeTrue();
94-
configs.ContainsKey("isenabled2").ShouldBeTrue();
96+
configs.ContainsKey("is_enabled_2").ShouldBeTrue();
9597
}
9698

9799
[Fact]
@@ -105,18 +107,18 @@ public void GetRuleIdConfiguration_ReturnsBuildRuleConfiguration()
105107
root=true
106108
107109
[*.csproj]
108-
build_check.rule_id.Severity=Error
109-
build_check.rule_id.EvaluationAnalysisScope=ProjectOnly
110+
build_check.rule_id.severity=error
111+
build_check.rule_id.scope=project
110112
""");
111113

112114
var configurationProvider = new ConfigurationProvider();
113115
var buildConfig = configurationProvider.GetUserConfiguration(Path.Combine(workFolder1.Path, "test.csproj"), "rule_id");
114116

115117
buildConfig.ShouldNotBeNull();
116118

117-
buildConfig.IsEnabled?.ShouldBeTrue();
118-
buildConfig.Severity?.ShouldBe(BuildAnalyzerResultSeverity.Error);
119-
buildConfig.EvaluationAnalysisScope?.ShouldBe(EvaluationAnalysisScope.ProjectOnly);
119+
buildConfig.IsEnabled.ShouldBe(true);
120+
buildConfig.Severity.ShouldBe(BuildAnalyzerResultSeverity.Error);
121+
buildConfig.EvaluationAnalysisScope.ShouldBe(EvaluationAnalysisScope.ProjectOnly);
120122
}
121123

122124
[Fact]
@@ -132,12 +134,12 @@ public void GetRuleIdConfiguration_CustomConfigurationValidity_NotValid_Differen
132134
[*.csproj]
133135
build_check.rule_id.property1=value1
134136
build_check.rule_id.property2=value2
135-
build_check.rule_id.isEnabled2=true
137+
build_check.rule_id.is_enabled_2=true
136138
137139
[test123.csproj]
138140
build_check.rule_id.property1=value2
139141
build_check.rule_id.property2=value3
140-
build_check.rule_id.isEnabled2=tru1
142+
build_check.rule_id.is_enabled_2=tru1
141143
""");
142144

143145
var configurationProvider = new ConfigurationProvider();
@@ -163,13 +165,13 @@ public void GetRuleIdConfiguration_CustomConfigurationValidity_NotValid_Differen
163165
[*.csproj]
164166
build_check.rule_id.property1=value1
165167
build_check.rule_id.property2=value2
166-
build_check.rule_id.isEnabled2=true
168+
build_check.rule_id.is_enabled_2=true
167169
168170
[test123.csproj]
169171
build_check.rule_id.property1=value1
170172
build_check.rule_id.property2=value2
171-
build_check.rule_id.isEnabled2=true
172-
build_check.rule_id.isEnabled3=true
173+
build_check.rule_id.is_enabled_2=true
174+
build_check.rule_id.is_enabled_3=true
173175
""");
174176

175177
var configurationProvider = new ConfigurationProvider();
@@ -195,12 +197,12 @@ public void GetRuleIdConfiguration_CustomConfigurationValidity_Valid()
195197
[*.csproj]
196198
build_check.rule_id.property1=value1
197199
build_check.rule_id.property2=value2
198-
build_check.rule_id.isEnabled2=true
200+
build_check.rule_id.is_enabled_2=true
199201
200202
[test123.csproj]
201203
build_check.rule_id.property1=value1
202204
build_check.rule_id.property2=value2
203-
build_check.rule_id.isEnabled2=true
205+
build_check.rule_id.is_enabled_2=true
204206
""");
205207

206208
var configurationProvider = new ConfigurationProvider();

0 commit comments

Comments
 (0)