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