diff --git a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs index 64e9db0175622cc77cab28efaba314fcaf50a66f..2d701abec16fff3065caf47f88a4bee1414cdad5 100644 --- a/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs +++ b/src/Compilers/CSharp/Test/CommandLine/CommandLineTests.cs @@ -11271,6 +11271,168 @@ class C { }"; CleanupAllGeneratedFiles(srcFile.Path); } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void CompilerWarnAsErrorDoesNotEmit(bool warnAsError) + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(@" +class C +{ + int _f; // CS0169: unused field +}"); + + var docName = "temp.xml"; + var pdbName = "temp.pdb"; + var additionalArgs = new[] { $"/doc:{docName}", $"/pdb:{pdbName}", "/debug" }; + if (warnAsError) + { + additionalArgs = additionalArgs.Append("/warnaserror").AsArray(); + } + + var expectedErrorCount = warnAsError ? 1 : 0; + var expectedWarningCount = !warnAsError ? 1 : 0; + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, + additionalArgs, + expectedErrorCount: expectedErrorCount, + expectedWarningCount: expectedWarningCount); + + var expectedOutput = warnAsError ? "error CS0169" : "warning CS0169"; + Assert.Contains(expectedOutput, output); + + string binaryPath = Path.Combine(dir.Path, "temp.dll"); + Assert.True(File.Exists(binaryPath) == !warnAsError); + + string pdbPath = Path.Combine(dir.Path, pdbName); + Assert.True(File.Exists(pdbPath) == !warnAsError); + + string xmlDocFilePath = Path.Combine(dir.Path, docName); + Assert.True(File.Exists(xmlDocFilePath) == !warnAsError); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void AnalyzerConfigSeverityEscalationToErrorDoesNotEmit(bool analyzerConfigSetToError) + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(@" +class C +{ + int _f; // CS0169: unused field +}"); + + var docName = "temp.xml"; + var pdbName = "temp.pdb"; + var additionalArgs = new[] { $"/doc:{docName}", $"/pdb:{pdbName}", "/debug" }; + + if (analyzerConfigSetToError) + { + var analyzerConfig = dir.CreateFile(".editorconfig").WriteAllText(@" +[*.cs] +dotnet_diagnostic.cs0169.severity = error"); + + additionalArgs = additionalArgs.Append("/analyzerconfig:" + analyzerConfig.Path).ToArray(); + } + + var expectedErrorCount = analyzerConfigSetToError ? 1 : 0; + var expectedWarningCount = !analyzerConfigSetToError ? 1 : 0; + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, + additionalArgs, + expectedErrorCount: expectedErrorCount, + expectedWarningCount: expectedWarningCount); + + var expectedOutput = analyzerConfigSetToError ? "error CS0169" : "warning CS0169"; + Assert.Contains(expectedOutput, output); + + string binaryPath = Path.Combine(dir.Path, "temp.dll"); + Assert.True(File.Exists(binaryPath) == !analyzerConfigSetToError); + + string pdbPath = Path.Combine(dir.Path, pdbName); + Assert.True(File.Exists(pdbPath) == !analyzerConfigSetToError); + + string xmlDocFilePath = Path.Combine(dir.Path, docName); + Assert.True(File.Exists(xmlDocFilePath) == !analyzerConfigSetToError); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void RulesetSeverityEscalationToErrorDoesNotEmit(bool rulesetSetToError) + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText(@" +class C +{ + int _f; // CS0169: unused field +}"); + + var docName = "temp.xml"; + var pdbName = "temp.pdb"; + var additionalArgs = new[] { $"/doc:{docName}", $"/pdb:{pdbName}", "/debug" }; + + if (rulesetSetToError) + { + string source = @" + + + + + +"; + var rulesetFile = CreateRuleSetFile(source); + additionalArgs = additionalArgs.Append("/ruleset:" + rulesetFile.Path).ToArray(); + } + + var expectedErrorCount = rulesetSetToError ? 1 : 0; + var expectedWarningCount = !rulesetSetToError ? 1 : 0; + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, + additionalArgs, + expectedErrorCount: expectedErrorCount, + expectedWarningCount: expectedWarningCount); + + var expectedOutput = rulesetSetToError ? "error CS0169" : "warning CS0169"; + Assert.Contains(expectedOutput, output); + + string binaryPath = Path.Combine(dir.Path, "temp.dll"); + Assert.True(File.Exists(binaryPath) == !rulesetSetToError); + + string pdbPath = Path.Combine(dir.Path, pdbName); + Assert.True(File.Exists(pdbPath) == !rulesetSetToError); + + string xmlDocFilePath = Path.Combine(dir.Path, docName); + Assert.True(File.Exists(xmlDocFilePath) == !rulesetSetToError); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void AnalyzerWarnAsErrorDoesNotEmit(bool warnAsError) + { + var dir = Temp.CreateDirectory(); + var src = dir.CreateFile("temp.cs").WriteAllText("class C { }"); + + var additionalArgs = warnAsError ? new[] { "/warnaserror" } : null; + var expectedErrorCount = warnAsError ? 1 : 0; + var expectedWarningCount = !warnAsError ? 1 : 0; + var output = VerifyOutput(dir, src, includeCurrentAssemblyAsAnalyzerReference: false, + additionalArgs, + expectedErrorCount: expectedErrorCount, + expectedWarningCount: expectedWarningCount, + analyzers: new[] { new WarningDiagnosticAnalyzer() }); + + var expectedDiagnosticSeverity = warnAsError ? "error" : "warning"; + Assert.Contains($"{expectedDiagnosticSeverity} {WarningDiagnosticAnalyzer.Warning01.Id}", output); + + string binaryPath = Path.Combine(dir.Path, "temp.dll"); + Assert.True(File.Exists(binaryPath) == !warnAsError); + } } [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] diff --git a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs index e12b3e17a0a305f89c223d53e6097a34365337cf..70419535560b9e08f33ab4d795730d3a66aa4983 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/CompilationEmitTests.cs @@ -18,7 +18,6 @@ using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; -using static Roslyn.Test.Utilities.SigningTestHelpers; namespace Microsoft.CodeAnalysis.CSharp.UnitTests.Emit { @@ -5163,5 +5162,95 @@ public void CompileAndVerifyModuleIncludesAllModules() Assert.Equal("refMod.netmodule", a.ContainingModule.Name); }); } + + [Fact] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void WarnAsErrorDoesNotEmit_GeneralDiagnosticOption() + { + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error); + TestWarnAsErrorDoesNotEmitCore(options); + } + + [Fact] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void WarnAsErrorDoesNotEmit_SpecificDiagnosticOption() + { + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithSpecificDiagnosticOptions("CS0169", ReportDiagnostic.Error); + TestWarnAsErrorDoesNotEmitCore(options); + } + + private void TestWarnAsErrorDoesNotEmitCore(CSharpCompilationOptions options) + { + string source = @" +class X +{ + int _f; +}"; + var compilation = CreateCompilation(source, options: options); + + using var output = new MemoryStream(); + using var pdbStream = new MemoryStream(); + using var xmlDocumentationStream = new MemoryStream(); + using var win32ResourcesStream = compilation.CreateDefaultWin32Resources(versionResource: true, noManifest: false, manifestContents: null, iconInIcoFormat: null); + + var emitResult = compilation.Emit(output, pdbStream, xmlDocumentationStream, win32ResourcesStream); + Assert.False(emitResult.Success); + + Assert.Equal(0, output.Length); + Assert.Equal(0, pdbStream.Length); + + // https://github.com/dotnet/roslyn/issues/37996 tracks revisiting the below behavior. + Assert.True(xmlDocumentationStream.Length > 0); + + emitResult.Diagnostics.Verify( + // (4,9): error CS0169: The field 'X._f' is never used + // int _f; + Diagnostic(ErrorCode.WRN_UnreferencedField, "_f").WithArguments("X._f").WithLocation(4, 9).WithWarningAsError(true)); + } + + [Fact] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void WarnAsErrorWithMetadataOnlyImageDoesEmit_GeneralDiagnosticOption() + { + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, generalDiagnosticOption: ReportDiagnostic.Error); + TestWarnAsErrorWithMetadataOnlyImageDoesEmitCore(options); + } + + [Fact] + [WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")] + public void WarnAsErrorWithMetadataOnlyImageDoesEmit_SpecificDiagnosticOptions() + { + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary).WithSpecificDiagnosticOptions("CS0612", ReportDiagnostic.Error); + TestWarnAsErrorWithMetadataOnlyImageDoesEmitCore(options); + } + + private void TestWarnAsErrorWithMetadataOnlyImageDoesEmitCore(CSharpCompilationOptions options) + { + string source = @" +public class X +{ + public void M(Y y) + { + } +} + +[System.Obsolete] +public class Y { } +"; + var compilation = CreateCompilation(source, options: options); + + using var output = new MemoryStream(); + var emitOptions = new EmitOptions(metadataOnly: true); + + var emitResult = compilation.Emit(output, options: emitOptions); + Assert.True(emitResult.Success); + + Assert.True(output.Length > 0); + + emitResult.Diagnostics.Verify( + // (4,19): error CS0612: 'Y' is obsolete + // public void M(Y y) + Diagnostic(ErrorCode.WRN_DeprecatedSymbol, "Y").WithArguments("Y").WithLocation(4, 19).WithWarningAsError(true)); + } } } diff --git a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs index acac0a78b769f272108abf6a9f99e68725fa3463..131972003c8da168d6684edfc27b2a8017308173 100644 --- a/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs +++ b/src/Compilers/Core/Portable/CommandLine/CommonCompiler.cs @@ -16,8 +16,6 @@ using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Utilities; -using static Microsoft.CodeAnalysis.AnalyzerConfig; -using TreeOptions = System.Collections.Immutable.ImmutableDictionary; namespace Microsoft.CodeAnalysis { @@ -564,11 +562,8 @@ internal bool ReportDiagnostics(IEnumerable diagnostics, TextWri /// are guaranteed to break the build. /// Only diagnostics which have default severity error and are tagged as NotConfigurable fall in this bucket. /// This includes all compiler error diagnostics and specific analyzer error diagnostics that are marked as not configurable by the analyzer author. - /// Note: does NOT do filtering, so it may return false if a - /// non-error diagnostic were later elevated to an error through filtering (e.g., through - /// warn-as-error). /// - internal static bool HasUnsuppressedErrors(DiagnosticBag diagnostics) + internal static bool HasUnsuppressableErrors(DiagnosticBag diagnostics) { foreach (var diag in diagnostics.AsEnumerable()) { @@ -580,6 +575,23 @@ internal static bool HasUnsuppressedErrors(DiagnosticBag diagnostics) return false; } + /// + /// Returns true if the bag has any diagnostics with effective Severity=Error. Also returns true for warnings or informationals + /// or warnings promoted to error via /warnaserror which are not suppressed. + /// + internal static bool HasUnsuppressedErrors(DiagnosticBag diagnostics) + { + foreach (Diagnostic diagnostic in diagnostics.AsEnumerable()) + { + if (diagnostic.IsUnsuppressedError) + { + return true; + } + } + + return false; + } + protected virtual void PrintError(Diagnostic diagnostic, TextWriter consoleOutput) { consoleOutput.WriteLine(DiagnosticFormatter.Format(diagnostic, Culture)); @@ -843,7 +855,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat // Print the diagnostics produced during the parsing stage and exit if there were any errors. compilation.GetDiagnostics(CompilationStage.Parse, includeEarlierStages: false, diagnostics, cancellationToken); - if (HasUnsuppressedErrors(diagnostics)) + if (HasUnsuppressableErrors(diagnostics)) { return; } @@ -891,7 +903,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat } compilation.GetDiagnostics(CompilationStage.Declare, includeEarlierStages: false, diagnostics, cancellationToken); - if (HasUnsuppressedErrors(diagnostics)) + if (HasUnsuppressableErrors(diagnostics)) { return; } @@ -964,6 +976,22 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat filterOpt: null, cancellationToken: cancellationToken); + // Prior to generating the xml documentation file, + // we apply programmatic suppressions for compiler warnings from diagnostic suppressors. + // If there are still any unsuppressed errors or warnings escalated to errors + // then we bail out from generating the documentation file. + // This maintains the compiler invariant that xml documentation file should not be + // generated in presence of diagnostics that break the build. + if (analyzerDriver != null && !diagnostics.IsEmptyWithoutResolution) + { + analyzerDriver.ApplyProgrammaticSuppressions(diagnostics, compilation); + } + + if (HasUnsuppressedErrors(diagnostics)) + { + success = false; + } + if (success) { // NOTE: as native compiler does, we generate the documentation file @@ -1003,7 +1031,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat { using (var win32ResourceStreamOpt = GetWin32Resources(MessageProvider, Arguments, compilation, diagnostics)) { - if (HasUnsuppressedErrors(diagnostics)) + if (HasUnsuppressableErrors(diagnostics)) { return; } @@ -1044,11 +1072,6 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat { // Apply diagnostic suppressions for analyzer and/or compiler diagnostics from diagnostic suppressors. analyzerDriver.ApplyProgrammaticSuppressions(diagnostics, compilation); - - if (HasUnsuppressedErrors(diagnostics)) - { - success = false; - } } } } @@ -1057,6 +1080,11 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat moduleBeingBuilt.CompilationFinished(); } + if (HasUnsuppressedErrors(diagnostics)) + { + success = false; + } + if (success) { var peStreamProvider = new CompilerEmitStreamProvider(this, finalPeFilePath); @@ -1104,7 +1132,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat } } - if (HasUnsuppressedErrors(diagnostics)) + if (HasUnsuppressableErrors(diagnostics)) { return; } @@ -1124,7 +1152,7 @@ private int RunCore(TextWriter consoleOutput, ErrorLogger errorLogger, Cancellat if (analyzerExceptionDiagnostics != null) { diagnostics.AddRange(analyzerExceptionDiagnostics); - if (HasUnsuppressedErrors(analyzerExceptionDiagnostics)) + if (HasUnsuppressableErrors(analyzerExceptionDiagnostics)) { return; } diff --git a/src/Compilers/Core/Portable/Compilation/Compilation.cs b/src/Compilers/Core/Portable/Compilation/Compilation.cs index 99c2b28593a5306e575468edd1b112066a9fe694..a5cfdf601eaef6d1e4c54a2aa7edce93c283601f 100644 --- a/src/Compilers/Core/Portable/Compilation/Compilation.cs +++ b/src/Compilers/Core/Portable/Compilation/Compilation.cs @@ -2489,6 +2489,8 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) if (!options.EmitMetadataOnly) { + // NOTE: We generate documentation even in presence of compile errors. + // https://github.com/dotnet/roslyn/issues/37996 tracks revisiting this behavior. if (!GenerateResourcesAndDocumentationComments( moduleBeingBuilt, xmlDocumentationStream, @@ -2517,6 +2519,11 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) privateKeyOpt = StrongNameKeys.PrivateKey; } + if (!options.EmitMetadataOnly && CommonCompiler.HasUnsuppressedErrors(diagnostics)) + { + success = false; + } + if (success) { success = SerializeToPeStream( @@ -2654,7 +2661,7 @@ internal void EnsureAnonymousTypeTemplates(CancellationToken cancellationToken) } } - if (CommonCompiler.HasUnsuppressedErrors(diagnostics)) + if (CommonCompiler.HasUnsuppressableErrors(diagnostics)) { return null; } diff --git a/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs b/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs index e784965d84db15b73b823c692f2a294ce4efdc32..3b8a38c0db75de1fb95ca7bad0f451eda1a6eb81 100644 --- a/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs +++ b/src/Compilers/Core/Portable/Diagnostic/Diagnostic.cs @@ -567,6 +567,12 @@ internal virtual bool IsNotConfigurable() /// internal bool IsUnsuppressableError() => DefaultSeverity == DiagnosticSeverity.Error && IsNotConfigurable(); + + /// + /// Returns true if this is a unsuppressed diagnostic with an effective error severity. + /// + internal bool IsUnsuppressedError + => Severity == DiagnosticSeverity.Error && !IsSuppressed; } /// diff --git a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs index b26f06c2e91ba280c51dc3aa8b7506d4f0bb86e7..884d35cb038cd545c6e01d770229e34f403a69fe 100644 --- a/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs +++ b/src/Compilers/Core/Portable/DiagnosticAnalyzer/AnalyzerDriver.cs @@ -35,6 +35,11 @@ internal abstract partial class AnalyzerDriver : IDisposable /// private readonly ConcurrentSet _programmaticSuppressions; + /// + /// Set of diagnostics that have already been processed for application of programmatic suppressions. + /// + private readonly ConcurrentSet _diagnosticsProcessedForProgrammaticSuppressions; + /// /// Flag indicating if the include any /// which can suppress reported analyzer/compiler diagnostics. @@ -168,6 +173,7 @@ protected AnalyzerDriver(ImmutableArray analyzers, AnalyzerM _isGeneratedCode = (tree, ct) => GeneratedCodeUtilities.IsGeneratedCode(tree, isComment, ct); _hasDiagnosticSuppressors = this.Analyzers.Any(a => a is DiagnosticSuppressor); _programmaticSuppressions = _hasDiagnosticSuppressors ? new ConcurrentSet() : null; + _diagnosticsProcessedForProgrammaticSuppressions = _hasDiagnosticSuppressors ? new ConcurrentSet(ReferenceEqualityComparer.Instance) : null; } /// @@ -631,42 +637,55 @@ private ImmutableArray ApplyProgrammaticSuppressionsCore(ImmutableAr Debug.Assert(_hasDiagnosticSuppressors); Debug.Assert(!reportedDiagnostics.IsEmpty); Debug.Assert(_programmaticSuppressions != null); + Debug.Assert(_diagnosticsProcessedForProgrammaticSuppressions != null); - // We do not allow analyzer based suppressions for following category of diagnostics: - // 1. Diagnostics which are already suppressed in source via pragma/suppress message attribute. - // 2. Diagnostics explicitly tagged as not configurable by analyzer authors - this includes compiler error diagnostics. - // 3. Diagnostics which are marked as error by default by diagnostic authors. - var suppressableDiagnostics = reportedDiagnostics.Where(d => !d.IsSuppressed && - !d.IsNotConfigurable() && - d.DefaultSeverity != DiagnosticSeverity.Error); - if (suppressableDiagnostics.IsEmpty()) + try { - return reportedDiagnostics; - } + // We do not allow analyzer based suppressions for following category of diagnostics: + // 1. Diagnostics which are already suppressed in source via pragma/suppress message attribute. + // 2. Diagnostics explicitly tagged as not configurable by analyzer authors - this includes compiler error diagnostics. + // 3. Diagnostics which are marked as error by default by diagnostic authors. + var suppressableDiagnostics = reportedDiagnostics.Where(d => !d.IsSuppressed && + !d.IsNotConfigurable() && + d.DefaultSeverity != DiagnosticSeverity.Error && + !_diagnosticsProcessedForProgrammaticSuppressions.Contains(d)); - executeSuppressionActions(suppressableDiagnostics, concurrent: compilation.Options.ConcurrentBuild); - if (_programmaticSuppressions.IsEmpty) - { - return reportedDiagnostics; - } + if (suppressableDiagnostics.IsEmpty()) + { + return reportedDiagnostics; + } - var builder = ArrayBuilder.GetInstance(reportedDiagnostics.Length); - ImmutableDictionary programmaticSuppressionsByDiagnostic = createProgrammaticSuppressionsByDiagnosticMap(_programmaticSuppressions); - foreach (var diagnostic in reportedDiagnostics) - { - if (programmaticSuppressionsByDiagnostic.TryGetValue(diagnostic, out var programmaticSuppressionInfo)) + executeSuppressionActions(suppressableDiagnostics, concurrent: compilation.Options.ConcurrentBuild); + if (_programmaticSuppressions.IsEmpty) { - Debug.Assert(suppressableDiagnostics.Contains(diagnostic)); - Debug.Assert(!diagnostic.IsSuppressed); - builder.Add(diagnostic.WithProgrammaticSuppression(programmaticSuppressionInfo)); + return reportedDiagnostics; } - else + + var builder = ArrayBuilder.GetInstance(reportedDiagnostics.Length); + ImmutableDictionary programmaticSuppressionsByDiagnostic = createProgrammaticSuppressionsByDiagnosticMap(_programmaticSuppressions); + foreach (var diagnostic in reportedDiagnostics) { - builder.Add(diagnostic); + if (programmaticSuppressionsByDiagnostic.TryGetValue(diagnostic, out var programmaticSuppressionInfo)) + { + Debug.Assert(suppressableDiagnostics.Contains(diagnostic)); + Debug.Assert(!diagnostic.IsSuppressed); + var suppressedDiagnostic = diagnostic.WithProgrammaticSuppression(programmaticSuppressionInfo); + Debug.Assert(suppressedDiagnostic.IsSuppressed); + builder.Add(suppressedDiagnostic); + } + else + { + builder.Add(diagnostic); + } } - } - return builder.ToImmutableAndFree(); + return builder.ToImmutableAndFree(); + } + finally + { + // Mark the reported diagnostics as processed for programmatic suppressions to avoid duplicate callbacks to suppressors for same diagnostics. + _diagnosticsProcessedForProgrammaticSuppressions.AddRange(reportedDiagnostics); + } void executeSuppressionActions(IEnumerable reportedDiagnostics, bool concurrent) { diff --git a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb index 9e087a02c0f504545cbe2c23b9b3b6f2eebc6205..2ae614f62a649ac6fa5bd601fa3d393fbe22f7eb 100644 --- a/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb +++ b/src/Compilers/VisualBasic/Test/CommandLine/CommandLineTests.vb @@ -9554,6 +9554,193 @@ End Class" CleanupAllGeneratedFiles(file.Path) End Sub + + + + + + Public Sub CompilerWarnAsErrorDoesNotEmit(ByVal warnAsError As Boolean) + ' warning BC40008 : 'C' is obsolete + Dim source = " +Imports System + + +Class C +End Class + +Class D + Inherits C +End Class" + Dim dir = Temp.CreateDirectory() + Dim file = dir.CreateFile("temp.vb") + file.WriteAllText(source) + + Dim docName As String = "doc.xml" + Dim additionalFlags = {$"/doc:{docName}", "/debug:full"} + If warnAsError Then + additionalFlags = additionalFlags.Append("/warnaserror").AsArray() + End If + + Dim expectedErrorCount = If(warnAsError, 1, 0) + Dim expectedWarningCount = If(Not warnAsError, 1, 0) + Dim output = VerifyOutput(dir, file, + includeCurrentAssemblyAsAnalyzerReference:=False, + additionalFlags, + expectedErrorCount:=expectedErrorCount, + expectedWarningCount:=expectedWarningCount) + + Dim expectedOutput = If(warnAsError, "error BC40008", "warning BC40008") + Assert.Contains(expectedOutput, output) + + Dim binaryPath As String = Path.Combine(dir.Path, "temp.dll") + Assert.True(IO.File.Exists(binaryPath) = Not warnAsError) + + Dim pdbPath As String = Path.Combine(dir.Path, "temp.pdb") + Assert.True(IO.File.Exists(pdbPath) = Not warnAsError) + + Dim docPath As String = Path.Combine(dir.Path, docName) + Assert.True(IO.File.Exists(docPath) = Not warnAsError) + End Sub + + + + + + Public Sub AnalyzerConfigSeverityEscalationToErrorDoesNotEmit(ByVal analyzerConfigSetToError As Boolean) + ' warning BC40008 : 'C' is obsolete + Dim source = " +Imports System + + +Class C +End Class + +Class D + Inherits C +End Class" + Dim dir = Temp.CreateDirectory() + Dim file = dir.CreateFile("temp.vb") + file.WriteAllText(source) + + Dim docName As String = "doc.xml" + Dim additionalFlags = {$"/doc:{docName}", "/debug:full"} + + If analyzerConfigSetToError Then + Dim analyzerConfig = dir.CreateFile(".editorconfig").WriteAllText(" +[*.vb] +dotnet_diagnostic.bc40008.severity = error") + + additionalFlags = additionalFlags.Append("/analyzerconfig:" + analyzerConfig.Path).ToArray() + End If + + Dim expectedErrorCount = If(analyzerConfigSetToError, 1, 0) + Dim expectedWarningCount = If(Not analyzerConfigSetToError, 1, 0) + Dim output = VerifyOutput(dir, file, + includeCurrentAssemblyAsAnalyzerReference:=False, + additionalFlags, + expectedErrorCount:=expectedErrorCount, + expectedWarningCount:=expectedWarningCount) + + Dim expectedOutput = If(analyzerConfigSetToError, "error BC40008", "warning BC40008") + Assert.Contains(expectedOutput, output) + + Dim binaryPath As String = Path.Combine(dir.Path, "temp.dll") + Assert.True(IO.File.Exists(binaryPath) = Not analyzerConfigSetToError) + + Dim pdbPath As String = Path.Combine(dir.Path, "temp.pdb") + Assert.True(IO.File.Exists(pdbPath) = Not analyzerConfigSetToError) + + Dim docPath As String = Path.Combine(dir.Path, docName) + Assert.True(IO.File.Exists(docPath) = Not analyzerConfigSetToError) + End Sub + + + + + + Public Sub RulesetSeverityEscalationToErrorDoesNotEmit(ByVal rulesetSetToError As Boolean) + ' warning BC40008 : 'C' is obsolete + Dim source = " +Imports System + + +Class C +End Class + +Class D + Inherits C +End Class" + Dim dir = Temp.CreateDirectory() + Dim file = dir.CreateFile("temp.vb") + file.WriteAllText(source) + + Dim docName As String = "doc.xml" + Dim additionalFlags = {$"/doc:{docName}", "/debug:full"} + + If rulesetSetToError Then + Dim rulesetSource = + + + + + + + Dim ruleSetFile = CreateRuleSetFile(rulesetSource) + + additionalFlags = additionalFlags.Append("/ruleset:" + ruleSetFile.Path).ToArray() + End If + + Dim expectedErrorCount = If(rulesetSetToError, 1, 0) + Dim expectedWarningCount = If(Not rulesetSetToError, 1, 0) + Dim output = VerifyOutput(dir, file, + includeCurrentAssemblyAsAnalyzerReference:=False, + additionalFlags, + expectedErrorCount:=expectedErrorCount, + expectedWarningCount:=expectedWarningCount) + + Dim expectedOutput = If(rulesetSetToError, "error BC40008", "warning BC40008") + Assert.Contains(expectedOutput, output) + + Dim binaryPath As String = Path.Combine(dir.Path, "temp.dll") + Assert.True(IO.File.Exists(binaryPath) = Not rulesetSetToError) + + Dim pdbPath As String = Path.Combine(dir.Path, "temp.pdb") + Assert.True(IO.File.Exists(pdbPath) = Not rulesetSetToError) + + Dim docPath As String = Path.Combine(dir.Path, docName) + Assert.True(IO.File.Exists(docPath) = Not rulesetSetToError) + End Sub + + + + + + Public Sub AnalyzerWarnAsErrorDoesNotEmit(ByVal warnAsError As Boolean) + Dim source = " +Class C +End Class" + Dim dir = Temp.CreateDirectory() + Dim file = dir.CreateFile("temp.vb") + file.WriteAllText(source) + + Dim expectedErrorCount = If(warnAsError, 2, 0) + Dim expectedWarningCount = If(Not warnAsError, 2, 0) + Dim analyzer As DiagnosticAnalyzer = New WarningDiagnosticAnalyzer() ' Reports 2 warnings for each named type. + Dim additionalFlags = If(warnAsError, {"/warnaserror"}, Nothing) + Dim output = VerifyOutput(dir, file, + includeCurrentAssemblyAsAnalyzerReference:=False, + additionalFlags, + expectedErrorCount:=expectedErrorCount, + expectedWarningCount:=expectedWarningCount, + analyzers:=ImmutableArray.Create(analyzer)) + + Dim expectedODiagnosticSeverity = If(warnAsError, "error", "warning") + Assert.Contains($"{expectedODiagnosticSeverity} {WarningDiagnosticAnalyzer.Warning01.Id}", output) + Assert.Contains($"{expectedODiagnosticSeverity} {WarningDiagnosticAnalyzer.Warning03.Id}", output) + + Dim binaryPath As String = Path.Combine(dir.Path, "temp.dll") + Assert.True(IO.File.Exists(binaryPath) = Not warnAsError) + End Sub End Class