-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Modify global analyzer config precedence: #45871
Modify global analyzer config precedence: #45871
Conversation
890c36e
to
47f9622
Compare
/// <summary> | ||
/// Get diagnostic severity set globally for a given diagnostic identifier | ||
/// </summary> | ||
public abstract bool TryGetGlobalDiagnosticValue(string diagnosticId, out ReportDiagnostic severity); | ||
} | ||
|
||
internal sealed class CompilerSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ This type is a new public api. Should we rename it to better reflect that it's not really about TreeOptions?
Perhaps DiagnosticSeverityProvider
? Although its based on top of analyzer options it really only returns ReportDiagnostics
#Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if there is time to rename APIs. Isn't the API locked with .NET 5 RC1 shipping?
In reply to: 452976584 [](ancestors = 452976584)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This better not be locked given we just had our review of APIs today and we have other things to change here too. 😄
@chsienki Just to make sure we understood, this needs to be a different API than TryGetGlobalDiagnosticValue because other options have to be slotted in the middle in terms of the precedence? And that one is just the compilation level diagnostic settings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TryGetGlobalDiagnosticValue
has to exist as the user has to have a way of asking for diagnostics that still apply when not in the context of a syntax tree.
My comment is that I think we should rename CompilerSyntaxTreeOptionsProvider
as we only use it to get diagnostics. Though i'm now wondering if keeping as TreeOptions is better as as it allows us to add things onto it in the future
In reply to: 473402105 [](ancestors = 473402105)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are not locked yet, we can still change them if needed.
} | ||
break; | ||
} | ||
} | ||
|
||
// 5. Global analyzer options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this should go immediately after // 3. Compilation level
check. Any rule ID specific configuration should still trump over generic diagnostic option handled in 4. Global options
and be done before pragma related checks. Basically, if global config options suppress a specific diagnostic ID, then it would need a rule ID specific configuration from user to override it. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah ok, that makes sense I think. I wasn't sure how we wanted to handle the precedence here.
So, if a NuGet package ups a specific diagnostic to a warning, but the user specifies the global -warn:0 then we would still expect to warn about that particular diagnostic?
#Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah right yes thinking about it, the user would say -warn:0, but then still have to explicity add a -nowarn for the one the global config upped. That makes sense I think. #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, any diagnostic ID specific setting should override diagnostic ID agnostic setting. That has been the common design for diagnostic ID configuration from all sources. #Resolved
@@ -335,6 +335,12 @@ internal bool IsSupportedDiagnostic(DiagnosticAnalyzer analyzer, Diagnostic diag | |||
isSuppressed = true; | |||
} | |||
|
|||
// Global editorconfig settings only apply if it wasn't supressed via commandline already | |||
if (!isSuppressed && options.SyntaxTreeOptionsProvider is object && options.SyntaxTreeOptionsProvider.TryGetGlobalDiagnosticValue(diag.Id, out severity)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it is better to instead move this check upfront based on precedence order:
// Is this diagnostic suppressed by default (as written by the rule author)
var isSuppressed = !diag.IsEnabledByDefault;
// Global editorconfig settings overrides the analyzer author
// Compilation wide user settings (diagnosticOptions) from ruleset/nowarn/warnaserror overrides the analyzer author and global editorconfig settings.
if (diagnosticOptions.TryGetValue(diag.Id, out var severity) ||
options.SyntaxTreeOptionsProvider is object && options.SyntaxTreeOptionsProvider.TryGetGlobalDiagnosticValue(diag.Id, out severity))
{
isSuppressed = severity == ReportDiagnostic.Suppress;
}
else
{
severity = isSuppressed ? ReportDiagnostic.Suppress : DiagnosticDescriptor.MapSeverityToReport(diag.DefaultSeverity);
}
``` #Resolved
@@ -176,6 +176,11 @@ Namespace Microsoft.CodeAnalysis.VisualBasic | |||
End If | |||
End If | |||
|
|||
Dim globalReport As ReportDiagnostic | |||
If (Not isSpecified) AndAlso syntaxTreeOptions IsNot Nothing AndAlso syntaxTreeOptions.TryGetGlobalDiagnosticValue(id, globalReport) Then |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same suggestion here. Rule ID specific global config options should override Rule ID agnostic options. #Resolved
10ea50a
to
26bf2ee
Compare
@dotnet/roslyn-compiler for review please. |
|
Could we add a quick summary of what the precedence is now to the description of the PR? #Resolved |
Ping @dotnet/roslyn-compiler for reviews please :) |
@@ -145,8 +145,35 @@ private AnalyzerConfigSet(ImmutableArray<AnalyzerConfig> analyzerConfigs, Global | |||
Debug.Assert(allMatchers.Count == _analyzerConfigs.Length); | |||
|
|||
_analyzerMatchers = allMatchers.ToImmutableAndFree(); | |||
|
|||
// parse the global options upfront |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the motivation to move this parsing of global options up? (why not keep it in GetOptionsForSourcePath
?) #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we're returning them both as a property and inline in GetOptionsForSourcePath
we should probably just make it lazy though, rather than doing it unconditionally.
In reply to: 471683120 [](ancestors = 471683120)
@@ -87,7 +87,8 @@ internal abstract partial class CommonCompiler | |||
TextWriter consoleOutput, | |||
TouchedFileLogger touchedFilesLogger, | |||
ErrorLogger errorLoggerOpt, | |||
ImmutableArray<AnalyzerConfigOptionsResult> analyzerConfigOptions); | |||
ImmutableArray<AnalyzerConfigOptionsResult> analyzerConfigOptions, | |||
AnalyzerConfigOptionsResult globalConfigOptions); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: since global options now take precedence, consider moving them up in the parameter list (before analyzerConfigOptions
). #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Global options are lower precedence than analyzer options.
In reply to: 471708791 [](ancestors = 471708791)
/// <summary> | ||
/// Get diagnostic severity set globally for a given diagnostic identifier | ||
/// </summary> | ||
public abstract bool TryGetGlobalDiagnosticValue(string diagnosticId, out ReportDiagnostic severity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TryGetGlobalDiagnosticValue [](start = 29, length = 27)
Sorry if this was already discussed with feature crew offline, but what consumer of our public API needs this new API?
I would have thought the existing TryGetDiagnosticValue
would be sufficient. #Closed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because global options can exist 'ambiently' without applying to any specific tree. One option would be to call TryGetDiagnosticValue
with a null
tree but that seems icky.
In reply to: 471710188 [](ancestors = 471710188)
Do we have a test for Refers to: src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs:1920 in 89ba3bf. [](commit_id = 89ba3bf2955022f11a1d0ab2b1c3b8b96df22f8e, deletion_comment = False) |
@@ -10020,6 +10020,58 @@ End Class") | |||
End If | |||
End Sub | |||
|
|||
<Fact> | |||
<WorkItem(44087, "https://github.com/dotnet/roslyn/issues/44804")> | |||
Public Sub GlobalAnalyzerConfigDiagnosticOptionsCanBeOverridenByCommandLine() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GlobalAnalyzerConfigDiagnosticOptionsCanBeOverridenByCommandLine [](start = 19, length = 64)
nit: thanks for naming tests to make intent clear. Makes the review easier :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (iteration 10)
Added In reply to: 675076806 [](ancestors = 675076806) Refers to: src/Compilers/Core/CodeAnalysisTest/Analyzers/AnalyzerConfigTests.cs:1920 in 89ba3bf. [](commit_id = 89ba3bf2955022f11a1d0ab2b1c3b8b96df22f8e, deletion_comment = False) |
Ping @dotnet/roslyn-compiler for a second review please :) |
@@ -119,7 +119,8 @@ internal static class CSharpDiagnosticFilter | |||
/// 1. Warning level | |||
/// 2. Syntax tree level | |||
/// 3. Compilation level | |||
/// 4. Global warning level | |||
/// 4. Global analyzer config | |||
/// 5. Global warning level |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is 'global warning level' different than 'warning level'? I'm trying to understand this list and keep coming up confused.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could probably make the wording clearer. Warning level is the /warn:3
that enables/disables big swathes of warnings.
Global warning level is /warnaserror:+/-
that bumps all warnings up to errors
In reply to: 473388737 [](ancestors = 473388737)
[CombinatorialData] | ||
public void TestAddingAndRemovingGlobalEditorConfigFileWithDiagnosticSeverity([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string languageName, bool useRecoverableTrees) | ||
{ | ||
using var workspace = useRecoverableTrees ? CreateWorkspaceWithRecoverableSyntaxTrees() : CreateWorkspace(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove the 'useRecoverableTrees' and make this just a regular [Fact]. The distinction mattered when diagnostic values were held on the trees since we could have bugs where we didn't handle the trees correctly in both cases. It's now moot. I'm guessing this test predates merging the other PR so you were following the correct pattern, at the time. #Resolved
@@ -26,17 +27,27 @@ public sealed class TestSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider | |||
x => x.Item2, | |||
comparer) | |||
); | |||
if (globalOption.Item1 is object) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we instead make globalOption a (string, ReportDiagnostic)
to make the null handling clearer? This file is #nullable enabled so presumably you shouldn't be able to get a null first parameter; the fact you can pass default in line 50 is a compiler bug: #38957 #Resolved
/// <summary> | ||
/// Get diagnostic severity set globally for a given diagnostic identifier | ||
/// </summary> | ||
public abstract bool TryGetGlobalDiagnosticValue(string diagnosticId, out ReportDiagnostic severity); | ||
} | ||
|
||
internal sealed class CompilerSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This better not be locked given we just had our review of APIs today and we have other things to change here too. 😄
@chsienki Just to make sure we understood, this needs to be a different API than TryGetGlobalDiagnosticValue because other options have to be slotted in the middle in terms of the precedence? And that one is just the compilation level diagnostic settings?
Do we have a document describing the precedence levels? If not we should create one and if we do we should update it with this PR. Assuming that "Syntax Tree Level" includes non-global analyzer configurations correct? #Resolved |
{ | ||
if (_lazyConfigOptions is null) | ||
{ | ||
Interlocked.CompareExchange( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the value in using this lazy pattern given that AnalyzerConfigOptionsResult
is a struct
? This property will always return a copy of the value, the ParseGlobalConfigOptions
can run multiple times even in this config. Can't see the value StrongBox<AnalyzerConfigOptionResult>
provides over AnalyzerConfigOptionResult?
(nullable value).
What am I missing here? #Resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because the struct is bigger than an IntPtr, assignment isn't guaranteed to be atomic (AFAIK). Means one thread could read from it and get a half initialized struct if we don't box + interlock exchange.
In reply to: 475294138 [](ancestors = 475294138)
private static void ParseSectionOptions(Section section, TreeOptions.Builder treeBuilder, AnalyzerOptions.Builder analyzerBuilder, ArrayBuilder<Diagnostic> diagnosticBuilder, string analyzerConfigPath, ConcurrentDictionary<ReadOnlyMemory<char>, string> diagIdCache) | ||
{ | ||
const string DiagnosticOptionPrefix = "dotnet_diagnostic."; | ||
const string DiagnosticOptionSuffix = ".severity"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Believe const
locals should be camel cased #Resolved
/// <summary> | ||
/// Get diagnostic severity set globally for a given diagnostic identifier | ||
/// </summary> | ||
public abstract bool TryGetGlobalDiagnosticValue(string diagnosticId, out ReportDiagnostic severity); | ||
} | ||
|
||
internal sealed class CompilerSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are not locked yet, we can still change them if needed.
@@ -1255,8 +1255,13 @@ private ImmutableHashSet<DiagnosticAnalyzer> ComputeSuppressedAnalyzersForTree(S | |||
var hasUnsuppressedDiagnostic = false; | |||
foreach (var descriptor in descriptors) | |||
{ | |||
if (!options.TryGetDiagnosticValue(tree, descriptor.Id, out var configuredSeverity) || | |||
configuredSeverity != ReportDiagnostic.Suppress) | |||
options.TryGetGlobalDiagnosticValue(descriptor.Id, out var configuredSeverity); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider explicitly discarding to acknowledge you're deliberately ignoring this result rather than accidentally doing so.
options.TryGetGlobalDiagnosticValue(descriptor.Id, out var configuredSeverity); | |
_ = options.TryGetGlobalDiagnosticValue(descriptor.Id, out var configuredSeverity); | |
``` #Resolved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The way you are using configuredSeverity
here implies that on a false
return the value is guaranteed to be default
. What happens if we pass a non-defaulted value into the API. Does it default
the value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It explicitly sets it to Default
(see https://github.com/dotnet/roslyn/pull/45871/files/55ac7e5acdd2a1f91625ce31caa99af5cc43b5b9#diff-ffb0437c61e5f1029ae0c11cb59507d9R89). It's an out
param though, so I don't understand how we'd pass in an explicit value?
In reply to: 475295255 [](ancestors = 475295255)
@@ -26,17 +27,27 @@ public sealed class TestSyntaxTreeOptionsProvider : SyntaxTreeOptionsProvider | |||
x => x.Item2, | |||
comparer) | |||
); | |||
if (globalOption.key is object) | |||
{ | |||
_globalOptions = new Dictionary<string, ReportDiagnostic>() { { globalOption.key, globalOption.diagnostic } }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use an explicit comparer. #Resolved
@@ -186,6 +187,11 @@ internal static class CSharpDiagnosticFilter | |||
// 3. Compilation level | |||
isSpecified = true; | |||
} | |||
else if (syntaxTreeOptions is object && syntaxTreeOptions.TryGetGlobalDiagnosticValue(id, out report)) | |||
{ | |||
// 4. Global analyzer config options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Crazy specific NIT: the rest of the comments match up with the comment verbiage at the top of the method.
// 4. Global analyzer config options | |
// 4. Global analyzer config level | |
``` #Resolved |
I don't believe have such a document, might be good to add something here: https://github.com/dotnet/roslyn/tree/master/docs/analyzers |
Ping @dotnet/roslyn-compiler for second review please :) |
- Pre-parse the global options into their own collection - Don't put global diagnostics on the tree level options - Add global diagnostics onto the tree options provider - Pass through the global diagnostics during compilation - Update the analyzer supression code - Add tests
Add tests to verify
add a test for no global config
7413077
to
daadd45
Compare
* upstream/master: (43 commits) Fix typos (dotnet#47264) Disable CS0660 where Full Solution Analysis produces a different result Pass in correct arguments to TypeScript handler Add skipped failing test for IDE0044 (dotnet#45288) Avoid first chance exception Code review feedback part 3 Update stale URLs in the readme Update IntegrationTestSourceGenerator.cs Fix comment Update IntegrationTestSourceGenerator.cs Remove unused reference to obsolete class Update nullable annotations in Classification folder Update generator public API names: (dotnet#47222) NullableContextAttribute for property parameters is from accessor (dotnet#47223) Modify global analyzer config precedence: (dotnet#45871) Skip the C# source generator integration test running for now Additional XAML LSP handlers (dotnet#47217) Remap diagnostics using IWorkspaceVenusSpanMappingService in-proc Warn on type named "record" (dotnet#47094) Bail out early before getting SyntaxTree ...
Fixes #44804
Precedence level is now: