Skip to content

Commit

Permalink
Correctly supress/elevate diagnostics produced by source generators (#…
Browse files Browse the repository at this point in the history
…52544)

* Correctly supress/elevate diagnostics produced by source generators
  • Loading branch information
chsienki authored Apr 13, 2021
1 parent e0bfe26 commit d43e561
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1358,5 +1358,138 @@ class C { }
Assert.True(tree.TryGetRoot(out var rootFromTryGetRoot));
Assert.Same(rootFromGetRoot, rootFromTryGetRoot);
}

[Fact]
public void Diagnostics_Respect_Suppression()
{
var source = @"
class C { }
";
var parseOptions = TestOptions.Regular;
Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();

Assert.Single(compilation.SyntaxTrees);
CallbackGenerator gen = new CallbackGenerator((c) => { }, (c) =>
{
c.ReportDiagnostic(CSDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 2));
c.ReportDiagnostic(CSDiagnostic.Create("GEN002", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 3));
});

var options = ((CSharpCompilationOptions)compilation.Options);

// generator driver diagnostics are reported seperately from the compilation
verifyDiagnosticsWithOptions(options,
Diagnostic("GEN001").WithLocation(1, 1),
Diagnostic("GEN002").WithLocation(1, 1));

// warnings can be individually suppressed
verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN001", ReportDiagnostic.Suppress),
Diagnostic("GEN002").WithLocation(1, 1));

verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN002", ReportDiagnostic.Suppress),
Diagnostic("GEN001").WithLocation(1, 1));

// warning level is respected
verifyDiagnosticsWithOptions(options.WithWarningLevel(0));

verifyDiagnosticsWithOptions(options.WithWarningLevel(2),
Diagnostic("GEN001").WithLocation(1, 1));

verifyDiagnosticsWithOptions(options.WithWarningLevel(3),
Diagnostic("GEN001").WithLocation(1, 1),
Diagnostic("GEN002").WithLocation(1, 1));

// warnings can be upgraded to errors
verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN001", ReportDiagnostic.Error),
Diagnostic("GEN001").WithLocation(1, 1).WithWarningAsError(true),
Diagnostic("GEN002").WithLocation(1, 1));

verifyDiagnosticsWithOptions(options.WithSpecificDiagnosticOptions("GEN002", ReportDiagnostic.Error),
Diagnostic("GEN001").WithLocation(1, 1),
Diagnostic("GEN002").WithLocation(1, 1).WithWarningAsError(true));


void verifyDiagnosticsWithOptions(CompilationOptions options, params DiagnosticDescription[] expected)
{
GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions: parseOptions);
var updatedCompilation = compilation.WithOptions(options);

driver.RunGeneratorsAndUpdateCompilation(updatedCompilation, out var outputCompilation, out var diagnostics);
outputCompilation.VerifyDiagnostics();
diagnostics.Verify(expected);
}
}

[Fact]
public void Diagnostics_Respect_Pragma_Suppression()
{
var gen001 = CSDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, true, 2);

// reported diagnostics can have a location in source
verifyDiagnosticsWithSource("//comment",
new[] { (gen001, TextSpan.FromBounds(2, 5)) },
Diagnostic("GEN001", "com").WithLocation(1, 3));

// diagnostics are suppressed via #pragma
verifyDiagnosticsWithSource(
@"#pragma warning disable
//comment",
new[] { (gen001, TextSpan.FromBounds(27, 30)) },
Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3));

// but not when they don't have a source location
verifyDiagnosticsWithSource(
@"#pragma warning disable
//comment",
new[] { (gen001, new TextSpan(0, 0)) },
Diagnostic("GEN001").WithLocation(1, 1));

// can be suppressed explicitly
verifyDiagnosticsWithSource(
@"#pragma warning disable GEN001
//comment",
new[] { (gen001, TextSpan.FromBounds(34, 37)) },
Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3));

// suppress + restore
verifyDiagnosticsWithSource(
@"#pragma warning disable GEN001
//comment
#pragma warning restore GEN001
//another",
new[] { (gen001, TextSpan.FromBounds(34, 37)), (gen001, TextSpan.FromBounds(77, 80)) },
Diagnostic("GEN001", "com", isSuppressed: true).WithLocation(2, 3),
Diagnostic("GEN001", "ano").WithLocation(4, 3));

void verifyDiagnosticsWithSource(string source, (Diagnostic, TextSpan)[] reportDiagnostics, params DiagnosticDescription[] expected)
{
var parseOptions = TestOptions.Regular;
source = source.Replace(Environment.NewLine, "\r\n");
Compilation compilation = CreateCompilation(source, sourceFileName: "sourcefile.cs", options: TestOptions.DebugDll, parseOptions: parseOptions);
compilation.VerifyDiagnostics();
Assert.Single(compilation.SyntaxTrees);

CallbackGenerator gen = new CallbackGenerator((c) => { }, (c) =>
{
foreach ((var d, var l) in reportDiagnostics)
{
if (l.IsEmpty)
{
c.ReportDiagnostic(d);
}
else
{
c.ReportDiagnostic(d.WithLocation(Location.Create(c.Compilation.SyntaxTrees.First(), l)));
}
}
});
GeneratorDriver driver = CSharpGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions: parseOptions);

driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics);
outputCompilation.VerifyDiagnostics();
diagnostics.Verify(expected);
}
}
}
}
22 changes: 19 additions & 3 deletions src/Compilers/Core/Portable/SourceGeneration/GeneratorDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,10 @@ internal GeneratorDriverState RunGeneratorsCore(Compilation compilation, Diagnos
continue;
}

(var sources, var diagnostics) = context.ToImmutableAndFree();
stateBuilder[i] = new GeneratorState(generatorState.Info, generatorState.PostInitTrees, ParseAdditionalSources(generator, sources, cancellationToken), diagnostics);
diagnosticsBag?.AddRange(diagnostics);
(var sources, var generatorDiagnostics) = context.ToImmutableAndFree();
generatorDiagnostics = FilterDiagnostics(compilation, generatorDiagnostics, driverDiagnostics: diagnosticsBag, cancellationToken);

stateBuilder[i] = new GeneratorState(generatorState.Info, generatorState.PostInitTrees, ParseAdditionalSources(generator, sources, cancellationToken), generatorDiagnostics);
}
state = state.With(generatorStates: stateBuilder.ToImmutableAndFree());
return state;
Expand Down Expand Up @@ -438,6 +439,21 @@ private static GeneratorState SetGeneratorException(CommonMessageProvider provid
return new GeneratorState(generatorState.Info, e, diagnostic);
}

private static ImmutableArray<Diagnostic> FilterDiagnostics(Compilation compilation, ImmutableArray<Diagnostic> generatorDiagnostics, DiagnosticBag? driverDiagnostics, CancellationToken cancellationToken)
{
ArrayBuilder<Diagnostic> filteredDiagnostics = ArrayBuilder<Diagnostic>.GetInstance();
foreach (var diag in generatorDiagnostics)
{
var filtered = compilation.Options.FilterDiagnostic(diag, cancellationToken);
if (filtered is object)
{
filteredDiagnostics.Add(filtered);
driverDiagnostics?.Add(filtered);
}
}
return filteredDiagnostics.ToImmutableAndFree();
}

internal static string GetFilePathPrefixForGenerator(ISourceGenerator generator)
{
var type = generator.GetType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
' See the LICENSE file in the project root for more information.

Imports System.Collections.Immutable
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Roslyn.Test.Utilities.TestGenerators

Expand Down Expand Up @@ -179,6 +181,80 @@ End Namespace
Assert.Same(rootFromGetRoot, rootFromTryGetRoot)
End Sub

<Fact>
Public Sub Diagnostics_Respect_Suppression()

Dim compilation As Compilation = GetCompilation(TestOptions.Regular)

Dim gen As CallbackGenerator = New CallbackGenerator(Sub(c)
End Sub,
Sub(c)
c.ReportDiagnostic(VBDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 2))
c.ReportDiagnostic(VBDiagnostic.Create("GEN002", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 3))
End Sub)

VerifyDiagnosticsWithOptions(gen, compilation,
Diagnostic("GEN001").WithLocation(1, 1),
Diagnostic("GEN002").WithLocation(1, 1))


Dim warnings As IDictionary(Of String, ReportDiagnostic) = New Dictionary(Of String, ReportDiagnostic)()
warnings.Add("GEN001", ReportDiagnostic.Suppress)
VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)),
Diagnostic("GEN002").WithLocation(1, 1))

warnings.Clear()
warnings.Add("GEN002", ReportDiagnostic.Suppress)
VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)),
Diagnostic("GEN001").WithLocation(1, 1))

warnings.Clear()
warnings.Add("GEN001", ReportDiagnostic.Error)
VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)),
Diagnostic("GEN001").WithLocation(1, 1).WithWarningAsError(True),
Diagnostic("GEN002").WithLocation(1, 1))

warnings.Clear()
warnings.Add("GEN002", ReportDiagnostic.Error)
VerifyDiagnosticsWithOptions(gen, compilation.WithOptions(compilation.Options.WithSpecificDiagnosticOptions(warnings)),
Diagnostic("GEN001").WithLocation(1, 1),
Diagnostic("GEN002").WithLocation(1, 1).WithWarningAsError(True))
End Sub

<Fact>
Public Sub Diagnostics_Respect_Pragma_Suppression()

Dim gen001 = VBDiagnostic.Create("GEN001", "generators", "message", DiagnosticSeverity.Warning, DiagnosticSeverity.Warning, True, 2)

VerifyDiagnosticsWithSource("'comment",
gen001, TextSpan.FromBounds(1, 4),
Diagnostic("GEN001", "com").WithLocation(1, 2))

VerifyDiagnosticsWithSource("#disable warning
'comment",
gen001, TextSpan.FromBounds(19, 22),
Diagnostic("GEN001", "com", isSuppressed:=True).WithLocation(2, 2))

VerifyDiagnosticsWithSource("#disable warning
'comment",
gen001, New TextSpan(0, 0),
Diagnostic("GEN001").WithLocation(1, 1))

VerifyDiagnosticsWithSource("#disable warning GEN001
'comment",
gen001, TextSpan.FromBounds(26, 29),
Diagnostic("GEN001", "com", isSuppressed:=True).WithLocation(2, 2))

VerifyDiagnosticsWithSource("#disable warning GEN001
'comment
#enable warning GEN001
'another",
gen001, TextSpan.FromBounds(60, 63),
Diagnostic("GEN001", "ano").WithLocation(4, 2))

End Sub


Shared Function GetCompilation(parseOptions As VisualBasicParseOptions, Optional source As String = "") As Compilation
If (String.IsNullOrWhiteSpace(source)) Then
source = "
Expand All @@ -194,6 +270,47 @@ End Class
Return compilation
End Function

Shared Sub VerifyDiagnosticsWithOptions(generator As ISourceGenerator, compilation As Compilation, ParamArray expected As DiagnosticDescription())

compilation.VerifyDiagnostics()
Assert.Single(compilation.SyntaxTrees)

Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(generator), parseOptions:=TestOptions.Regular)

Dim outputCompilation As Compilation = Nothing
Dim diagnostics As ImmutableArray(Of Diagnostic) = Nothing
driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, diagnostics)
outputCompilation.VerifyDiagnostics()

diagnostics.Verify(expected)
End Sub

Shared Sub VerifyDiagnosticsWithSource(source As String, diag As Diagnostic, location As TextSpan, ParamArray expected As DiagnosticDescription())
Dim parseOptions = TestOptions.Regular
source = source.Replace(Environment.NewLine, vbCrLf)
Dim compilation As Compilation = CreateCompilation(source)
compilation.VerifyDiagnostics()
Assert.Single(compilation.SyntaxTrees)

Dim gen As ISourceGenerator = New CallbackGenerator(Sub(c)
End Sub,
Sub(c)
If location.IsEmpty Then
c.ReportDiagnostic(diag)
Else
c.ReportDiagnostic(diag.WithLocation(CodeAnalysis.Location.Create(c.Compilation.SyntaxTrees.First(), location)))
End If
End Sub)

Dim driver As GeneratorDriver = VisualBasicGeneratorDriver.Create(ImmutableArray.Create(gen), parseOptions:=TestOptions.Regular)

Dim outputCompilation As Compilation = Nothing
Dim diagnostics As ImmutableArray(Of Diagnostic) = Nothing
driver.RunGeneratorsAndUpdateCompilation(compilation, outputCompilation, diagnostics)
outputCompilation.VerifyDiagnostics()

diagnostics.Verify(expected)
End Sub
End Class


Expand Down

0 comments on commit d43e561

Please sign in to comment.