/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs
2 162 строки
93 KB
Tomáš Matoušek
Track GeneratedFilesOutputDirectory in the IDE (#75311)
18 окт 2024, 22:45
Не верифицирован
18 окт 2024, 22:45
8d79420
Код
Авторство
О чём код?
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Text; using System.Threading; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Test.Utilities.TestGenerators; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.Semantic.UnitTests.SourceGeneration { public class SyntaxAwareGeneratorTests : CSharpTestBase { [Fact] public void Syntax_Receiver_Is_Present_When_Registered() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()), onExecute: (e) => receiver = e.SyntaxReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxReceiver>(receiver); } [Fact] public void Syntax_Receiver_Is_Null_WhenNot_Registered() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => { }, onExecute: (e) => receiver = e.SyntaxReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _); Assert.Null(receiver); } [Fact] public void SyntaxContext_Receiver_Is_Present_When_Registered() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxContextReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()), onExecute: (e) => receiver = e.SyntaxContextReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxContextReceiver>(receiver); } [Fact] public void SyntaxContext_Receiver_Is_Null_WhenNot_Registered() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxContextReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => { }, onExecute: (e) => receiver = e.SyntaxContextReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out _); Assert.Null(receiver); } [Fact] public void SyntaxContext_Receiver_Is_Null_When_Syntax_Receiver_Registered() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? syntaxReceiver = null; ISyntaxContextReceiver? contextReceiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()), onExecute: (e) => { syntaxReceiver = e.SyntaxReceiver; contextReceiver = e.SyntaxContextReceiver; } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.Null(contextReceiver); Assert.NotNull(syntaxReceiver); } [Fact] public void Syntax_Receiver_Is_Null_When_SyntaxContext_Receiver_Registered() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? syntaxReceiver = null; ISyntaxContextReceiver? contextReceiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()), onExecute: (e) => { syntaxReceiver = e.SyntaxReceiver; contextReceiver = e.SyntaxContextReceiver; } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.Null(syntaxReceiver); Assert.NotNull(contextReceiver); } [Fact] public void Syntax_Receiver_Can_Be_Registered_Only_Once() { // ISyntaxReceiver + ISyntaxReceiver GeneratorInitializationContext init = new GeneratorInitializationContext(CancellationToken.None); init.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()); Assert.Throws<InvalidOperationException>(() => { init.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()); }); // ISyntaxContextReceiver + ISyntaxContextReceiver init = new GeneratorInitializationContext(CancellationToken.None); init.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()); Assert.Throws<InvalidOperationException>(() => { init.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()); }); // ISyntaxContextReceiver + ISyntaxReceiver init = new GeneratorInitializationContext(CancellationToken.None); init.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()); Assert.Throws<InvalidOperationException>(() => { init.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()); }); // ISyntaxReceiver + ISyntaxContextReceiver init = new GeneratorInitializationContext(CancellationToken.None); init.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()); Assert.Throws<InvalidOperationException>(() => { init.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()); }); } [Fact] public void Syntax_Receiver_Visits_Syntax_In_Compilation() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()), onExecute: (e) => receiver = e.SyntaxReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxReceiver>(receiver); TestSyntaxReceiver testReceiver = (TestSyntaxReceiver)receiver!; Assert.Equal(21, testReceiver.VisitedNodes.Count); Assert.IsType<CompilationUnitSyntax>(testReceiver.VisitedNodes[0]); } [Fact] public void SyntaxContext_Receiver_Visits_Syntax_In_Compilation() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxContextReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver()), onExecute: (e) => receiver = e.SyntaxContextReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxContextReceiver>(receiver); TestSyntaxContextReceiver testReceiver = (TestSyntaxContextReceiver)receiver!; Assert.Equal(21, testReceiver.VisitedNodes.Count); Assert.IsType<CompilationUnitSyntax>(testReceiver.VisitedNodes[0].Node); Assert.NotNull(testReceiver.VisitedNodes[0].SemanticModel); Assert.Equal(testReceiver.VisitedNodes[0].SemanticModel.SyntaxTree, testReceiver.VisitedNodes[0].Node.SyntaxTree); } [Fact] public void Syntax_Receiver_Is_Not_Reused_Between_Non_Cached_Invocations() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? receiver = null; int invocations = 0; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(++invocations)), onExecute: (e) => receiver = e.SyntaxReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxReceiver>(receiver); TestSyntaxReceiver testReceiver = (TestSyntaxReceiver)receiver!; Assert.Equal(1, testReceiver.Tag); Assert.Equal(21, testReceiver.VisitedNodes.Count); Assert.IsType<CompilationUnitSyntax>(testReceiver.VisitedNodes[0]); // update the compilation. In v1 we always re-created the receiver, but in v2 we only re-create // it if the compilation has changed. compilation = compilation.WithAssemblyName("modified"); var previousReceiver = receiver; driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.NotEqual(receiver, previousReceiver); testReceiver = (TestSyntaxReceiver)receiver!; Assert.Equal(2, testReceiver.Tag); Assert.Equal(21, testReceiver.VisitedNodes.Count); Assert.IsType<CompilationUnitSyntax>(testReceiver.VisitedNodes[0]); } [Fact] public void Syntax_Receiver_Exception_During_Creation() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var exception = new Exception("Test Exception"); var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications((SyntaxReceiverCreator)(() => throw exception)), onExecute: (e) => { Assert.True(false); } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Empty(results.GeneratedTrees); Assert.Single(results.Diagnostics); Assert.Single(results.Results); Assert.Single(results.Results[0].Diagnostics); Assert.NotNull(results.Results[0].Exception); Assert.Equal("Test Exception", results.Results[0].Exception?.Message); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(CallbackGenerator), "Test Exception"); } [Fact] public void Syntax_Receiver_Exception_During_Visit() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var exception = new Exception("Test Exception"); var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) throw exception; })), onExecute: (e) => { e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Empty(results.GeneratedTrees); Assert.Single(results.Diagnostics); Assert.Single(results.Results); Assert.Single(results.Results[0].Diagnostics); Assert.NotNull(results.Results[0].Exception); Assert.Equal("Test Exception", results.Results[0].Exception?.Message); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(CallbackGenerator), "Test Exception"); } [Fact] public void Syntax_Receiver_Exception_During_Visit_Stops_Visits_On_Other_Trees() { var source1 = @" class C { int Property { get; set; } } "; var source2 = @" class D { public void Method() { } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(new[] { source1, source2 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Equal(2, compilation.SyntaxTrees.Count()); TestSyntaxReceiver receiver1 = new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is PropertyDeclarationSyntax) throw new Exception("Test Exception"); }); var testGenerator1 = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => receiver1), onExecute: (e) => { } ); TestSyntaxReceiver receiver2 = new TestSyntaxReceiver(tag: 1); var testGenerator2 = new CallbackGenerator2( onInit: (i) => i.RegisterForSyntaxNotifications(() => receiver2), onExecute: (e) => { } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator1, testGenerator2 }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.DoesNotContain(receiver1.VisitedNodes, n => n is MethodDeclarationSyntax); Assert.Contains(receiver2.VisitedNodes, n => n is MethodDeclarationSyntax); } [Fact] public void Syntax_Receiver_Exception_During_Visit_Doesnt_Stop_Other_Receivers() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var exception = new Exception("Test Exception"); var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) throw exception; })), onExecute: (e) => { } ); ISyntaxReceiver? receiver = null; var testGenerator2 = new CallbackGenerator2( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 1)), onExecute: (e) => { receiver = e.SyntaxReceiver; e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator, testGenerator2 }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Single(results.GeneratedTrees); Assert.Single(results.Diagnostics); Assert.Equal(2, results.Results.Length); Assert.Single(results.Results[0].Diagnostics); Assert.NotNull(results.Results[0].Exception); Assert.Equal("Test Exception", results.Results[0].Exception?.Message); Assert.Empty(results.Results[1].Diagnostics); var testReceiver = (TestSyntaxReceiver)receiver!; Assert.Equal(1, testReceiver.Tag); Assert.Equal(21, testReceiver.VisitedNodes.Count); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(CallbackGenerator), "Test Exception"); } [Fact] public void Syntax_Receiver_Is_Not_Created_If_Exception_During_Initialize() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); TestSyntaxReceiver? receiver = null; var exception = new Exception("test exception"); var testGenerator = new CallbackGenerator( onInit: (i) => { i.RegisterForSyntaxNotifications(() => receiver = new TestSyntaxReceiver()); throw exception; }, onExecute: (e) => { Assert.True(false); } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Null(receiver); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(CallbackGenerator), "test exception", initialization: true); } [Fact] public void Syntax_Receiver_Return_Null_During_Creation() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? syntaxRx = null; ISyntaxContextReceiver? syntaxContextRx = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications((SyntaxReceiverCreator)(() => null!)), onExecute: (e) => { syntaxRx = e.SyntaxReceiver; syntaxContextRx = e.SyntaxContextReceiver; } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); outputDiagnostics.Verify(); var results = driver.GetRunResult(); Assert.Empty(results.GeneratedTrees); Assert.Null(syntaxContextRx); Assert.Null(syntaxRx); } [Fact] public void Syntax_Receiver_Is_Not_Created_If_Exception_During_PostInitialize() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); TestSyntaxReceiver? receiver = null; var exception = new Exception("test exception"); var testGenerator = new CallbackGenerator( onInit: (i) => { i.RegisterForSyntaxNotifications(() => receiver = new TestSyntaxReceiver()); i.RegisterForPostInitialization((pic) => throw exception); }, onExecute: (e) => { Assert.True(false); } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Null(receiver); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(CallbackGenerator), "test exception", initialization: true); } [Fact] public void Syntax_Receiver_Visits_Syntax_Added_In_PostInit() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var source2 = @" class D { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => { i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()); i.RegisterForPostInitialization((pic) => pic.AddSource("postInit", source2)); }, onExecute: (e) => receiver = e.SyntaxReceiver ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxReceiver>(receiver); TestSyntaxReceiver testReceiver = (TestSyntaxReceiver)receiver!; var classDeclarations = testReceiver.VisitedNodes.OfType<ClassDeclarationSyntax>().Select(c => c.Identifier.Text); Assert.Equal(new[] { "C", "D" }, classDeclarations); } [Fact] public void Syntax_Receiver_Visits_Syntax_Added_In_PostInit_From_Other_Generator() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var source2 = @" class D { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? receiver = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver()), onExecute: (e) => receiver = e.SyntaxReceiver ); var testGenerator2 = new CallbackGenerator2( onInit: (i) => i.RegisterForPostInitialization((pic) => pic.AddSource("postInit", source2)), onExecute: (e) => { } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator, testGenerator2 }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); Assert.NotNull(receiver); Assert.IsType<TestSyntaxReceiver>(receiver); TestSyntaxReceiver testReceiver = (TestSyntaxReceiver)receiver!; var classDeclarations = testReceiver.VisitedNodes.OfType<ClassDeclarationSyntax>().Select(c => c.Identifier.Text); Assert.Equal(new[] { "C", "D" }, classDeclarations); } [Fact] public void Syntax_Receiver_Can_Access_Types_Added_In_PostInit() { var source = @" class C : D { } "; var postInitSource = @" class D { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); Assert.Single(compilation.SyntaxTrees); compilation.VerifyDiagnostics( // (2,11): error CS0246: The type or namespace name 'D' could not be found (are you missing a using directive or an assembly reference?) // class C : D Diagnostic(ErrorCode.ERR_SingleTypeNameNotFound, "D").WithArguments("D").WithLocation(2, 11) ); var testGenerator = new CallbackGenerator( onInit: (i) => { i.RegisterForSyntaxNotifications(() => new TestSyntaxContextReceiver(callback: (ctx) => { if (ctx.Node is ClassDeclarationSyntax cds && cds.Identifier.Value?.ToString() == "C") { // ensure we can query the semantic model for D var dType = ctx.SemanticModel.Compilation.GetTypeByMetadataName("D"); Assert.NotNull(dType); Assert.False(dType.IsErrorType()); // and the code referencing it now works var typeInfo = ctx.SemanticModel.GetTypeInfo(cds.BaseList!.Types[0].Type); Assert.Same(dType, typeInfo.Type); } })); i.RegisterForPostInitialization((pic) => pic.AddSource("postInit", postInitSource)); }, onExecute: (e) => { } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out _); } [Fact] public void SyntaxContext_Receiver_Return_Null_During_Creation() { var source = @" class C { } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); ISyntaxReceiver? syntaxRx = null; ISyntaxContextReceiver? syntaxContextRx = null; var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications((SyntaxContextReceiverCreator)(() => null!)), onExecute: (e) => { syntaxRx = e.SyntaxReceiver; syntaxContextRx = e.SyntaxContextReceiver; } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); outputDiagnostics.Verify(); var results = driver.GetRunResult(); Assert.Empty(results.GeneratedTrees); Assert.Null(syntaxContextRx); Assert.Null(syntaxRx); } [Fact] public void IncrementalGenerator_With_Syntax_Filter() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); } [Fact] public void IncrementalGenerator_With_Multiple_Filters() { var source1 = @" #pragma warning disable CS0414 class classC { string fieldA = null; } "; var source2 = @" #pragma warning disable CS0414 class classD { } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(new[] { source1, source2 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); }); var source2 = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is ClassDeclarationSyntax fds, (c, _) => ((ClassDeclarationSyntax)c.Node).Identifier.ValueText); context.RegisterSourceOutput(source2, (spc, className) => { spc.AddSource(className, ""); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("classC.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("classD.cs", results.GeneratedTrees[2].FilePath); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Does_Not_Run_When_Not_Changed() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); List<string> syntaxFilterVisited = new(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => { if (c is FieldDeclarationSyntax fds) { syntaxFilterVisited.Add(fds.Declaration.Variables[0].Identifier.ValueText); return true; } return false; }, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); }); }); // Don't enable incremental tracking here as incremental tracking disables the "unchanged compilation implies unchanged syntax trees" optimization. GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Equal(new[] { "fieldA", "fieldB", "fieldC" }, syntaxFilterVisited); syntaxFilterVisited.Clear(); // run again on the *same* compilation driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Empty(syntaxFilterVisited); // now change the compilation, but don't change the syntax trees compilation = compilation.WithAssemblyName("newCompilation"); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Equal(new[] { "fieldA", "fieldB", "fieldC" }, syntaxFilterVisited); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Added_Tree() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; }"; var source2 = @" #pragma warning disable CS0414 class D { string fieldD = null; string fieldE = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText).WithTrackingName("Fields"); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); // add the second tree and re-run compilation = compilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(source2, parseOptions)); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(5, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.EndsWith("fieldD.cs", results.GeneratedTrees[3].FilePath); Assert.EndsWith("fieldE.cs", results.GeneratedTrees[4].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldD", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldE", IncrementalStepRunReason.New), output))); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Removed_Tree() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; }"; var source2 = @" #pragma warning disable CS0414 class D { string fieldD = null; string fieldE = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(new[] { source1, source2 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText).WithTrackingName("Fields"); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(5, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.EndsWith("fieldD.cs", results.GeneratedTrees[3].FilePath); Assert.EndsWith("fieldE.cs", results.GeneratedTrees[4].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldD", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldE", IncrementalStepRunReason.New), output))); // remove the second tree and re-run compilation = compilation.RemoveSyntaxTrees(compilation.SyntaxTrees.Last()); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldD", IncrementalStepRunReason.Removed), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldE", IncrementalStepRunReason.Removed), output))); } [Fact] [WorkItem(58647, "https://github.com/dotnet/roslyn/issues/58647")] public void IncrementalGenerator_With_Syntax_Filter_Removed_Tree_Add_New_Generator() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; }"; var source2 = @" #pragma warning disable CS0414 class D { string fieldD = null; string fieldE = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(new[] { source1, source2 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); context.RegisterSourceOutput(source, (spc, fieldName) => { }); }); var testGenerator2 = new PipelineCallbackGenerator2(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); context.RegisterSourceOutput(source, (spc, fieldName) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation); // remove the second tree, and add the new generator compilation = compilation.RemoveSyntaxTrees(compilation.SyntaxTrees.Last()); driver = driver.AddGenerators(ImmutableArray.Create(testGenerator2.AsSourceGenerator())); driver = driver.RunGenerators(compilation); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Runs_Only_For_Changed_Trees() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; }"; var source2 = @" #pragma warning disable CS0414 class D { string fieldB = null; }"; var source3 = @" #pragma warning disable CS0414 class E { string fieldC = null; }"; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(new[] { source1, source2, source3 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); List<string> fieldsCalledFor = new List<string>(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText).WithTrackingName("Fields"); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); fieldsCalledFor.Add(fieldName); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); // edit one of the syntax trees var firstTree = compilation.SyntaxTrees.First(); var newTree = CSharpSyntaxTree.ParseText(@" #pragma warning disable CS0414 class F { string fieldD = null; }", parseOptions); compilation = compilation.ReplaceSyntaxTree(firstTree, newTree); fieldsCalledFor.Clear(); // now re-run the drivers driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); // we produced the expected modified sources, but only called for the one different tree Assert.EndsWith("fieldD.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldD", IncrementalStepRunReason.Modified), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Unchanged), output))); Assert.Equal("fieldD", Assert.Single(fieldsCalledFor)); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Doesnt_Run_When_Compilation_Is_Unchanged() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; }"; var source2 = @" #pragma warning disable CS0414 class D { string fieldB = null; }"; var source3 = @" #pragma warning disable CS0414 class E { string fieldC = null; }"; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(new[] { source1, source2, source3 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); List<string> fieldsCalledFor = new List<string>(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText).WithTrackingName("Fields"); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); fieldsCalledFor.Add(fieldName); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create( [new IncrementalGeneratorWrapper(testGenerator)], parseOptions: parseOptions, driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: false)); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Equal(new[] { "fieldA", "fieldB", "fieldC" }, fieldsCalledFor); // now re-run the drivers with the same compilation fieldsCalledFor.Clear(); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); // we produced the expected modified sources, but didn't call for any of the trees Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Empty(fieldsCalledFor); // now re-run the drivers with the same compilation again to ensure we cached the trees correctly fieldsCalledFor.Clear(); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); // we produced the expected modified sources, but didn't call for any of the trees Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Empty(fieldsCalledFor); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_And_Changed_Tree_Order() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; }"; var source2 = @" #pragma warning disable CS0414 class D { string fieldB = null; }"; var source3 = @" #pragma warning disable CS0414 class E { string fieldC = null; }"; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(new[] { source1, source2, source3 }, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); List<string> syntaxFieldsCalledFor = new List<string>(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => { if (c is FieldDeclarationSyntax fds) { syntaxFieldsCalledFor.Add(fds.Declaration.Variables[0].Identifier.ValueText); return true; } return false; }, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText).WithTrackingName("Syntax") .Select((s, ct) => s).WithTrackingName("Fields"); context.RegisterSourceOutput(source, (spc, fieldName) => { spc.AddSource(fieldName, ""); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create( new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); Assert.Equal("fieldA", syntaxFieldsCalledFor[0]); Assert.Equal("fieldB", syntaxFieldsCalledFor[1]); Assert.Equal("fieldC", syntaxFieldsCalledFor[2]); //swap the order of the first and last trees var firstTree = compilation.SyntaxTrees.First(); var lastTree = compilation.SyntaxTrees.Last(); var dummyTree = CSharpSyntaxTree.ParseText("", parseOptions); compilation = compilation.ReplaceSyntaxTree(firstTree, dummyTree) .ReplaceSyntaxTree(lastTree, firstTree) .ReplaceSyntaxTree(dummyTree, lastTree); // now re-run the drivers and confirm we didn't actually run the node selector for any nodes. // Verify that we still ran the transform. syntaxFieldsCalledFor.Clear(); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Syntax"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Unchanged), output))); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.Cached), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Cached), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Cached), output))); Assert.Empty(syntaxFieldsCalledFor); // swap a tree for a tree with the same contents, but a new reference var newLastTree = CSharpSyntaxTree.ParseText(lastTree.ToString(), parseOptions); compilation = compilation.ReplaceSyntaxTree(firstTree, dummyTree) .ReplaceSyntaxTree(lastTree, firstTree) .ReplaceSyntaxTree(dummyTree, newLastTree); // now re-run the drivers and confirm we only ran the selector for the 'new' syntax tree // but then cached the result after the transform when we got the same value out syntaxFieldsCalledFor.Clear(); driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Empty(results.Diagnostics); Assert.Equal(3, results.GeneratedTrees.Length); Assert.EndsWith("fieldA.cs", results.GeneratedTrees[0].FilePath); Assert.EndsWith("fieldB.cs", results.GeneratedTrees[1].FilePath); Assert.EndsWith("fieldC.cs", results.GeneratedTrees[2].FilePath); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.Cached), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Cached), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Cached), output))); Assert.Equal("fieldC", Assert.Single(syntaxFieldsCalledFor)); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Can_Have_Comparer() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var source2 = @" #pragma warning disable CS0414 class C { string fieldD = null; string fieldE = null; string fieldF = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); source = source.WithComparer(new LambdaComparer<string>((a, b) => true)).WithTrackingName("Fields"); context.RegisterSourceOutput(source, (spc, fieldName) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create( new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); // make a change to the syntax tree compilation = compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), CSharpSyntaxTree.ParseText(source2, parseOptions)); // when we run it again, we get cached steps with the original values because the comparer has suppressed the modification. // the original value is preserved to ensure that separate runs of the generator have the same output when the user-provided comparer returns true. driver = driver.RunGenerators(compilation); results = driver.GetRunResult(); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.Unchanged), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.Unchanged), output))); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_And_Comparer_Doesnt_Do_Duplicate_Work() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); List<string> syntaxCalledFor = new List<string>(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => { syntaxCalledFor.Add(((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); return ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText; }); source = source.WithComparer(new LambdaComparer<string>((a, b) => false)); source = source.WithComparer(new LambdaComparer<string>((a, b) => false)); source = source.WithComparer(new LambdaComparer<string>((a, b) => false)); source = source.WithComparer(new LambdaComparer<string>((a, b) => false)); context.RegisterSourceOutput(source, (spc, fieldName) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation); // verify we only call the syntax transform once, even though we created multiple nodes via the withComparer Assert.Equal(new[] { "fieldA", "fieldB", "fieldC" }, syntaxCalledFor); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_And_Comparer_Can_Feed_Two_Outputs() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var source2 = @" #pragma warning disable CS0414 class C { string fieldD = null; string fieldE = null; string fieldF = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); List<string> syntaxCalledFor = new List<string>(); List<string> noCompareCalledFor = new List<string>(); List<string> compareCalledFor = new List<string>(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => { syntaxCalledFor.Add(((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText); return ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText; }); context.RegisterSourceOutput(source, (spc, fieldName) => { noCompareCalledFor.Add(fieldName); }); var comparerSource = source.WithComparer(new LambdaComparer<string>((a, b) => true)); context.RegisterSourceOutput(comparerSource, (spc, fieldName) => { compareCalledFor.Add(fieldName); }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation); // verify we ran the syntax transform twice, one for each output, even though we duplicated them Assert.Equal(new[] { "fieldA", "fieldB", "fieldC", "fieldA", "fieldB", "fieldC" }, syntaxCalledFor); Assert.Equal(new[] { "fieldA", "fieldB", "fieldC" }, noCompareCalledFor); Assert.Equal(new[] { "fieldA", "fieldB", "fieldC" }, compareCalledFor); // make a change to the syntax tree compilation = compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), CSharpSyntaxTree.ParseText(source2, parseOptions)); // now, when we re-run, both transforms will run, but the comparer will suppress the modified output syntaxCalledFor.Clear(); noCompareCalledFor.Clear(); compareCalledFor.Clear(); driver = driver.RunGenerators(compilation); Assert.Equal(new[] { "fieldD", "fieldE", "fieldF", "fieldD", "fieldE", "fieldF" }, syntaxCalledFor); Assert.Equal(new[] { "fieldD", "fieldE", "fieldF" }, noCompareCalledFor); Assert.Empty(compareCalledFor); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Can_Feed_Two_Outputs() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => { return ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText; }).WithTrackingName("Fields"); context.RegisterSourceOutput(source.Select((s, ct) => $"Output1_{s}").WithTrackingName("Output"), (spc, fieldName) => { }); context.RegisterSourceOutput(source.Select((s, ct) => $"Output2_{s}").WithTrackingName("Output"), (spc, fieldName) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); // verify we ran the syntax transform once, but fed both outputs var results = driver.GetRunResult(); Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); Assert.Collection(results.Results[0].TrackedSteps["Output"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("Output1_fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("Output1_fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("Output1_fieldC", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("Output2_fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("Output2_fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("Output2_fieldC", IncrementalStepRunReason.New), output))); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_Isnt_Duplicated_By_Combines() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => { return ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText; }).WithTrackingName("Fields"); var source2 = source.Combine(context.AdditionalTextsProvider.Collect()) .Combine(context.AnalyzerConfigOptionsProvider) .Combine(context.ParseOptionsProvider); context.RegisterSourceOutput(source2.Select((value, ct) => value.Left.Left.Left).WithTrackingName("Output"), (spc, output) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); // verify we only ran the syntax transform once, even though we called through a join Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); Assert.Collection(results.Results[0].TrackedSteps["Output"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); } [Fact] public void IncrementalGenerator_With_Syntax_Filter_And_Comparer_Survive_Combines() { var source1 = @" #pragma warning disable CS0414 class C { string fieldA = null; string fieldB = null; string fieldC = null; } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source1, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); var testGenerator = new PipelineCallbackGenerator(context => { var source = context.SyntaxProvider.CreateSyntaxProvider((c, _) => c is FieldDeclarationSyntax fds, (c, _) => { return ((FieldDeclarationSyntax)c.Node).Declaration.Variables[0].Identifier.ValueText; }).WithTrackingName("Fields"); var comparerSource = source.WithComparer(new LambdaComparer<string>((a, b) => false)); // now join the two sources together var joinedSource = source.Combine(comparerSource.Collect()); context.RegisterSourceOutput(joinedSource.Select((value, ct) => value.Left).WithTrackingName("Output"), (spc, fieldName) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions, driverOptions: TestOptions.GeneratorDriverOptions); driver = driver.RunGenerators(compilation); var results = driver.GetRunResult(); // verify we ran the syntax transform twice, one for each input node, but only called into the output once Assert.Collection(results.Results[0].TrackedSteps["Fields"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); Assert.Collection(results.Results[0].TrackedSteps["Output"], step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldA", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldB", IncrementalStepRunReason.New), output)), step => Assert.Collection(step.Outputs, output => Assert.Equal(("fieldC", IncrementalStepRunReason.New), output))); } [Fact] public void IncrementalGenerator_Throws_In_Syntax_Filter() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular.WithLanguageVersion(LanguageVersion.Preview); Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var exception = new Exception("Test Exception"); var testGenerator = new PipelineCallbackGenerator(ctx => { ctx.RegisterSourceOutput(ctx.SyntaxProvider.CreateSyntaxProvider((s, _) => { if (s is AssignmentExpressionSyntax) throw exception; return true; }, (c, _) => c.Node), (spc, s) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Empty(results.GeneratedTrees); Assert.Single(results.Diagnostics); Assert.Single(results.Results[0].Diagnostics); Assert.NotNull(results.Results[0].Exception); Assert.Equal("Test Exception", results.Results[0].Exception?.Message); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(PipelineCallbackGenerator), "Test Exception"); } [Fact] public void IncrementalGenerator_Throws_In_Syntax_Transform() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular.WithLanguageVersion(LanguageVersion.Preview); Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var exception = new Exception("Test Exception"); var testGenerator = new PipelineCallbackGenerator(ctx => { ctx.RegisterSourceOutput(ctx.SyntaxProvider.CreateSyntaxProvider<object>((s, _) => s is AssignmentExpressionSyntax, (c, _) => throw exception), (spc, s) => { }); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator) }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Empty(results.GeneratedTrees); Assert.Single(results.Diagnostics); Assert.Single(results.Results[0].Diagnostics); Assert.NotNull(results.Results[0].Exception); Assert.Equal("Test Exception", results.Results[0].Exception?.Message); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(PipelineCallbackGenerator), "Test Exception"); } [Fact] public void IncrementalGenerator_Throws_In_Syntax_Transform_Doesnt_Stop_Other_Generators() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular.WithLanguageVersion(LanguageVersion.Preview); Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var exception = new Exception("Test Exception"); var testGenerator = new PipelineCallbackGenerator(ctx => { ctx.RegisterSourceOutput(ctx.SyntaxProvider.CreateSyntaxProvider<object>((s, _) => s is AssignmentExpressionSyntax, (c, _) => throw exception), (spc, s) => { }); }); var testGenerator2 = new PipelineCallbackGenerator2(ctx => { ctx.RegisterSourceOutput(ctx.CompilationProvider, (spc, s) => spc.AddSource("test", "")); }); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { new IncrementalGeneratorWrapper(testGenerator), new IncrementalGeneratorWrapper(testGenerator2) }, parseOptions: parseOptions); driver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var outputDiagnostics); var results = driver.GetRunResult(); Assert.Single(results.GeneratedTrees); Assert.Single(results.Diagnostics); Assert.Single(results.Results[0].Diagnostics); Assert.NotNull(results.Results[0].Exception); Assert.Equal("Test Exception", results.Results[0].Exception?.Message); Assert.Single(results.Results[1].GeneratedSources); Assert.Null(results.Results[1].Exception); GeneratorDriverTests.VerifyGeneratorExceptionDiagnostic<Exception>(outputDiagnostics.Single(), nameof(PipelineCallbackGenerator), "Test Exception"); } [Fact] public void Incremental_Generators_Can_Be_Cancelled_During_Syntax() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); CancellationTokenSource cts = new CancellationTokenSource(); int filterCalled = 0; var generator = new IncrementalGeneratorWrapper(new PipelineCallbackGenerator((ctx) => { var step1 = ctx.SyntaxProvider.CreateSyntaxProvider((c, ct) => { filterCalled++; if (c is AssignmentExpressionSyntax) cts.Cancel(); return true; }, (a, _) => a); ctx.RegisterSourceOutput(step1, (spc, c) => spc.AddSource("step1", "")); })); GeneratorDriver driver = CSharpGeneratorDriver.Create(new ISourceGenerator[] { generator }, parseOptions: parseOptions); Assert.Throws<OperationCanceledException>(() => driver = driver.RunGenerators(compilation, cancellationToken: cts.Token)); Assert.Equal(19, filterCalled); } [Fact] public void Incremental_Generators_Can_Be_Cancelled_During_Syntax_And_Stop_Other_SyntaxVisits() { var source = @" class C { } "; var parseOptions = TestOptions.RegularPreview; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); CancellationTokenSource cts = new CancellationTokenSource(); bool generatorCancelled = false; var generator = new IncrementalGeneratorWrapper(new PipelineCallbackGenerator((ctx) => { var step1 = ctx.SyntaxProvider.CreateSyntaxProvider((c, ct) => { generatorCancelled = true; cts.Cancel(); return true; }, (a, _) => a); ctx.RegisterSourceOutput(step1, (spc, c) => spc.AddSource("step1", "")); var step2 = ctx.SyntaxProvider.CreateSyntaxProvider((c, ct) => { return true; }, (a, _) => a); ctx.RegisterSourceOutput(step2, (spc, c) => spc.AddSource("step2", "")); })); GeneratorDriver driver = CSharpGeneratorDriver.Create(new ISourceGenerator[] { generator }, parseOptions: parseOptions); Assert.Throws<OperationCanceledException>(() => driver = driver.RunGenerators(compilation, cancellationToken: cts.Token)); Assert.True(generatorCancelled); } [Fact] public void Syntax_Receiver_Cancellation_During_Visit() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; var parseOptions = TestOptions.Regular; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll, parseOptions: parseOptions); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var testGenerator = new CallbackGenerator( onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax) { throw new OperationCanceledException("Simulated cancellation from external source"); } })), onExecute: (e) => { e.AddSource("test", SourceText.From("public class D{}", Encoding.UTF8)); } ); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator }, parseOptions: parseOptions); driver = driver.RunGenerators(compilation, CancellationToken.None); var results = driver.GetRunResult(); Assert.Single(results.Results); Assert.IsType<OperationCanceledException>(results.Results[0].Exception); Assert.Equal("Simulated cancellation from external source", results.Results[0].Exception!.Message); } [Fact(Skip = "https://github.com/dotnet/roslyn/issues/61540: Timing is unreliable in CI")] public void Syntax_Provider_Doesnt_Attribute_Incorrect_Timing() { var source = @" class C { int Property { get; set; } void Function() { var x = 5; x += 4; } } "; Compilation compilation = CreateCompilation(source, options: TestOptions.DebugDll); compilation.VerifyDiagnostics(); Assert.Single(compilation.SyntaxTrees); var sleepTimeInMs = 50; var testGenerator = new PipelineCallbackGenerator(ctx => { ctx.RegisterSourceOutput(ctx.SyntaxProvider.CreateSyntaxProvider<object>((s, _) => s is AssignmentExpressionSyntax, (c, _) => { Thread.Sleep(sleepTimeInMs); return true; }), (spc, s) => { }); }).AsSourceGenerator(); var testGenerator2 = new PipelineCallbackGenerator2(ctx => { ctx.RegisterSourceOutput(ctx.SyntaxProvider.CreateSyntaxProvider<object>((s, _) => s is AssignmentExpressionSyntax, (c, _) => { Thread.Sleep(sleepTimeInMs); return true; }), (spc, s) => { }); ctx.RegisterSourceOutput(ctx.SyntaxProvider.CreateSyntaxProvider<object>((s, _) => s is AssignmentExpressionSyntax, (c, _) => { Thread.Sleep(sleepTimeInMs); return true; }), (spc, s) => { }); }).AsSourceGenerator(); GeneratorDriver driver = CSharpGeneratorDriver.Create(new[] { testGenerator, testGenerator2 }); driver = driver.RunGenerators(compilation); var timing = driver.GetTimingInfo(); Assert.NotEqual(TimeSpan.Zero, timing.ElapsedTime); Assert.Equal(2, timing.GeneratorTimes.Length); // check generator one took at least 'sleepTimeInMs' var timing1 = timing.GeneratorTimes[0]; Assert.Equal(testGenerator, timing1.Generator); Assert.NotEqual(TimeSpan.Zero, timing1.ElapsedTime); Assert.True(timing.ElapsedTime >= timing1.ElapsedTime); Assert.True(timing1.ElapsedTime.TotalMilliseconds >= sleepTimeInMs); // check generator two took at least 'sleepTimeInMs' * 2 var timing2 = timing.GeneratorTimes[1]; Assert.Equal(testGenerator2, timing2.Generator); Assert.NotEqual(TimeSpan.Zero, timing2.ElapsedTime); Assert.True(timing.ElapsedTime >= timing2.ElapsedTime); Assert.True(timing2.ElapsedTime.TotalMilliseconds >= sleepTimeInMs * 2); // now check that generator two took longer than generator one (and one didn't get attributed the time) Assert.True(timing2.ElapsedTime > timing1.ElapsedTime); } private class TestReceiverBase<T> { private readonly Action<T>? _callback; public List<T> VisitedNodes { get; } = new List<T>(); public int Tag { get; } public TestReceiverBase(int tag = 0, Action<T>? callback = null) { Tag = tag; _callback = callback; } public void OnVisitSyntaxNode(T syntaxNode) { VisitedNodes.Add(syntaxNode); if (_callback is object) { _callback(syntaxNode); } } } private class TestSyntaxReceiver : TestReceiverBase<SyntaxNode>, ISyntaxReceiver { public TestSyntaxReceiver(int tag = 0, Action<SyntaxNode>? callback = null) : base(tag, callback) { } } private class TestSyntaxContextReceiver : TestReceiverBase<GeneratorSyntaxContext>, ISyntaxContextReceiver { public TestSyntaxContextReceiver(int tag = 0, Action<GeneratorSyntaxContext>? callback = null) : base(tag, callback) { } } } }