diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 8750e3bd20534..a3895e32b279e 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -12290,6 +12290,44 @@ public void TestAnalyzerFilteringBasedOnSeverity(DiagnosticSeverity defaultSever } } + [WorkItem(47017, "https://github.com/dotnet/roslyn/issues/47017")] + [CombinatorialData, Theory] + public void TestWarnAsErrorMinusDoesNotEnableDisabledByDefaultAnalyzers(DiagnosticSeverity defaultSeverity, bool isEnabledByDefault) + { + // This test verifies that '/warnaserror-:DiagnosticId' does not affect if analyzers are executed or skipped.. + // Setup the analyzer to always throw an exception on analyzer callbacks for cases where we expect analyzer execution to be skipped: + // 1. Disabled by default analyzer, i.e. 'isEnabledByDefault == false'. + // 2. Default severity Hidden/Info: We only execute analyzers reporting Warning/Error severity diagnostics on command line builds. + var analyzerShouldBeSkipped = !isEnabledByDefault || + defaultSeverity == DiagnosticSeverity.Hidden || + defaultSeverity == DiagnosticSeverity.Info; + var analyzer = new NamedTypeAnalyzerWithConfigurableEnabledByDefault(isEnabledByDefault, defaultSeverity, throwOnAllNamedTypes: analyzerShouldBeSkipped); + var diagnosticId = analyzer.Descriptor.Id; + + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("test.cs").WriteAllText(@"class C { }"); + + // Verify '/warnaserror-:DiagnosticId' behavior. + var args = new[] { "/warnaserror+", $"/warnaserror-:{diagnosticId}", "/nologo", "/t:library", "/preferreduilang:en", src.Path }; + + var cmd = CreateCSharpCompiler(null, dir.Path, args, analyzers: ImmutableArray.Create(analyzer)); + var outWriter = new StringWriter(CultureInfo.InvariantCulture); + var exitCode = cmd.Run(outWriter); + var expectedExitCode = !analyzerShouldBeSkipped && defaultSeverity == DiagnosticSeverity.Error ? 1 : 0; + Assert.Equal(expectedExitCode, exitCode); + + var output = outWriter.ToString(); + if (analyzerShouldBeSkipped) + { + Assert.Empty(output); + } + else + { + var prefix = defaultSeverity == DiagnosticSeverity.Warning ? "warning" : "error"; + Assert.Contains($"{prefix} {diagnosticId}: {analyzer.Descriptor.MessageFormat}", output); + } + } + [Fact] public void SourceGenerators_EmbeddedSources() { diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs index ccf9341398742..83e158b5dc6cc 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerManager.cs @@ -321,7 +321,10 @@ internal bool IsDiagnosticAnalyzerSuppressed( var isSuppressed = !diag.IsEnabledByDefault; // Compilation wide user settings from ruleset/nowarn/warnaserror overrides the analyzer author. - if (diagnosticOptions.TryGetValue(diag.Id, out var severity)) + // Note that "/warnaserror-:DiagnosticId" adds a diagnostic option with value 'ReportDiagnostic.Default', + // which should not alter 'isSuppressed'. + if (diagnosticOptions.TryGetValue(diag.Id, out var severity) && + severity != ReportDiagnostic.Default) { isSuppressed = severity == ReportDiagnostic.Suppress; } diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index 3a0f2ee92fb6d..66fa5a3e8f649 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -10021,6 +10021,43 @@ End Class") End If End Sub + + + Public Sub TestWarnAsErrorMinusDoesNotEnableDisabledByDefaultAnalyzers(defaultSeverity As DiagnosticSeverity, isEnabledByDefault As Boolean) + ' This test verifies that '/warnaserror-:DiagnosticId' does not affect if analyzers are executed or skipped. + ' Setup the analyzer to always throw an exception on analyzer callbacks for cases where we expect analyzer execution to be skipped: + ' 1. Disabled by default analyzer, i.e. 'isEnabledByDefault == false'. + ' 2. Default severity Hidden/Info: We only execute analyzers reporting Warning/Error severity diagnostics on command line builds. + Dim analyzerShouldBeSkipped = Not isEnabledByDefault OrElse + defaultSeverity = DiagnosticSeverity.Hidden OrElse + defaultSeverity = DiagnosticSeverity.Info + + Dim analyzer = New NamedTypeAnalyzerWithConfigurableEnabledByDefault(isEnabledByDefault, defaultSeverity, throwOnAllNamedTypes:=analyzerShouldBeSkipped) + Dim diagnosticId = analyzer.Descriptor.Id + + Dim dir = Temp.CreateDirectory() + Dim src = dir.CreateFile("test.cs").WriteAllText(" +Class C +End Class") + + ' Verify '/warnaserror-:DiagnosticId' behavior. + Dim args = {"/warnaserror+", $"/warnaserror-:{diagnosticId}", "/nologo", "/t:library", "/preferreduilang:en", src.Path} + + Dim cmd = New MockVisualBasicCompiler(Nothing, dir.Path, args, analyzer) + Dim outWriter = New StringWriter(CultureInfo.InvariantCulture) + Dim exitCode = cmd.Run(outWriter) + Dim expectedExitCode = If(Not analyzerShouldBeSkipped AndAlso defaultSeverity = DiagnosticSeverity.[Error], 1, 0) + Assert.Equal(expectedExitCode, exitCode) + + Dim output = outWriter.ToString() + If analyzerShouldBeSkipped Then + Assert.Empty(output) + Else + Dim prefix = If(defaultSeverity = DiagnosticSeverity.Warning, "warning", "error") + Assert.Contains($"{prefix} {diagnosticId}: {analyzer.Descriptor.MessageFormat}", output) + End If + End Sub + Public Sub TestAdditionalFileAnalyzer(registerFromInitialize As Boolean) Dim srcDirectory = Temp.CreateDirectory()