提交 e4d465e2 编写于 作者: M Manish Vasani 提交者: Andy Gocke

Bail out from emit in presence of warnings reported as errors (#37948)

Command line compilation is fixed to avoid writing any files to disk when compilation
fails. The Emit API is changed to not write the PE file when compilation fails, but the
behavior where the XML doc comments are still written is preserved.

Fixes #37779
上级 e1d88751
......@@ -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 = @"<?xml version=""1.0"" encoding=""utf-8""?>
<RuleSet Name=""Ruleset1"" Description=""Test"" ToolsVersion=""12.0"">
<Rules AnalyzerId=""Microsoft.CodeAnalysis"" RuleNamespace=""Microsoft.CodeAnalysis"">
<Rule Id=""CS0169"" Action=""Error"" />
</Rules>
</RuleSet>
";
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)]
......
......@@ -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));
}
}
}
......@@ -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<string, Microsoft.CodeAnalysis.ReportDiagnostic>;
namespace Microsoft.CodeAnalysis
{
......@@ -564,11 +562,8 @@ internal bool ReportDiagnostics(IEnumerable<DiagnosticInfo> 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).
/// </summary>
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;
}
/// <summary>
/// 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.
/// </summary>
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;
}
......
......@@ -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;
}
......
......@@ -567,6 +567,12 @@ internal virtual bool IsNotConfigurable()
/// </summary>
internal bool IsUnsuppressableError()
=> DefaultSeverity == DiagnosticSeverity.Error && IsNotConfigurable();
/// <summary>
/// Returns true if this is a unsuppressed diagnostic with an effective error severity.
/// </summary>
internal bool IsUnsuppressedError
=> Severity == DiagnosticSeverity.Error && !IsSuppressed;
}
/// <summary>
......
......@@ -35,6 +35,11 @@ internal abstract partial class AnalyzerDriver : IDisposable
/// </summary>
private readonly ConcurrentSet<Suppression> _programmaticSuppressions;
/// <summary>
/// Set of diagnostics that have already been processed for application of programmatic suppressions.
/// </summary>
private readonly ConcurrentSet<Diagnostic> _diagnosticsProcessedForProgrammaticSuppressions;
/// <summary>
/// Flag indicating if the <see cref="Analyzers"/> include any <see cref="DiagnosticSuppressor"/>
/// which can suppress reported analyzer/compiler diagnostics.
......@@ -168,6 +173,7 @@ protected AnalyzerDriver(ImmutableArray<DiagnosticAnalyzer> analyzers, AnalyzerM
_isGeneratedCode = (tree, ct) => GeneratedCodeUtilities.IsGeneratedCode(tree, isComment, ct);
_hasDiagnosticSuppressors = this.Analyzers.Any(a => a is DiagnosticSuppressor);
_programmaticSuppressions = _hasDiagnosticSuppressors ? new ConcurrentSet<Suppression>() : null;
_diagnosticsProcessedForProgrammaticSuppressions = _hasDiagnosticSuppressors ? new ConcurrentSet<Diagnostic>(ReferenceEqualityComparer.Instance) : null;
}
/// <summary>
......@@ -631,42 +637,55 @@ private ImmutableArray<Diagnostic> 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<Diagnostic>.GetInstance(reportedDiagnostics.Length);
ImmutableDictionary<Diagnostic, ProgrammaticSuppressionInfo> 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<Diagnostic>.GetInstance(reportedDiagnostics.Length);
ImmutableDictionary<Diagnostic, ProgrammaticSuppressionInfo> 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<Diagnostic> reportedDiagnostics, bool concurrent)
{
......
......@@ -9554,6 +9554,193 @@ End Class"
CleanupAllGeneratedFiles(file.Path)
End Sub
<Theory>
<InlineData(True)>
<InlineData(False)>
<WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")>
Public Sub CompilerWarnAsErrorDoesNotEmit(ByVal warnAsError As Boolean)
' warning BC40008 : 'C' is obsolete
Dim source = "
Imports System
<Obsolete>
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
<Theory>
<InlineData(True)>
<InlineData(False)>
<WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")>
Public Sub AnalyzerConfigSeverityEscalationToErrorDoesNotEmit(ByVal analyzerConfigSetToError As Boolean)
' warning BC40008 : 'C' is obsolete
Dim source = "
Imports System
<Obsolete>
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
<Theory>
<InlineData(True)>
<InlineData(False)>
<WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")>
Public Sub RulesetSeverityEscalationToErrorDoesNotEmit(ByVal rulesetSetToError As Boolean)
' warning BC40008 : 'C' is obsolete
Dim source = "
Imports System
<Obsolete>
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 = <?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Ruleset1" Description="Test" ToolsVersion="12.0">
<Rules AnalyzerId="Microsoft.CodeAnalysis" RuleNamespace="Microsoft.CodeAnalysis">
<Rule Id="BC40008" Action="Error"/>
</Rules>
</RuleSet>
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
<Theory>
<InlineData(True)>
<InlineData(False)>
<WorkItem(37779, "https://github.com/dotnet/roslyn/issues/37779")>
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
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册