From 9d6a9750f8f4c125b39facae58b73d0a95c059d9 Mon Sep 17 00:00:00 2001 From: Timothy Makkison Date: Sun, 1 Feb 2026 17:35:07 +0000 Subject: [PATCH] perf: make `StaticPropertyInitializationGenerator` incremental --- .../StaticPropertyInitializationGenerator.cs | 268 +++++++++++------- .../Models/PropertyWithDataSource.cs | 22 +- .../AotConverterGeneratorIncrementalTests.cs | 2 +- ...InitializationGeneratorIncrementalTests.cs | 117 ++++++++ .../TestHelper.cs | 26 ++ 5 files changed, 324 insertions(+), 111 deletions(-) create mode 100644 TUnit.SourceGenerator.IncrementalTests/StaticPropertyInitializationGeneratorIncrementalTests.cs diff --git a/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs b/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs index 05f435af76..14bd519081 100644 --- a/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs +++ b/TUnit.Core.SourceGenerator/CodeGenerators/StaticPropertyInitializationGenerator.cs @@ -13,6 +13,8 @@ namespace TUnit.Core.SourceGenerator.CodeGenerators; [Generator] public class StaticPropertyInitializationGenerator : IIncrementalGenerator { + public const string ParseStaticProperties = "ParseStaticProperties"; + public void Initialize(IncrementalGeneratorInitializationContext context) { var enabledProvider = context.AnalyzerConfigOptionsProvider @@ -25,58 +27,45 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var testClasses = context.SyntaxProvider .CreateSyntaxProvider( predicate: static (s, _) => s is ClassDeclarationSyntax, - transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)) - .Where(static t => t is not null) + transform: static (ctx, _) => ctx) .Collect() - .Combine(enabledProvider); - - context.RegisterSourceOutput(testClasses, (sourceProductionContext, data) => - { - var (classes, isEnabled) = data; - if (!isEnabled) - { - return; - } + .Combine(enabledProvider) + .Select((classesProviderPair, _) => + ParseStaticPropertyInitializers(classesProviderPair.Left, classesProviderPair.Right)) + .WithTrackingName(ParseStaticProperties); - GenerateStaticPropertyInitialization(sourceProductionContext, classes.Where(t => t != null).ToImmutableArray()!); - }); + context.RegisterSourceOutput(testClasses, GenerateStaticPropertyInitialization); } - private static INamedTypeSymbol? GetSemanticTargetForGeneration(GeneratorSyntaxContext context) + private static EquatableArray ParseStaticPropertyInitializers(ImmutableArray classesContext, bool enabledProvider) { - if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol typeSymbol) + if (!enabledProvider) { - return null; - } - - // Skip open generic types - we can't generate code for types with unbound type parameters - // The initialization will happen in the consuming assembly that provides concrete type arguments - if (typeSymbol.IsGenericType && typeSymbol.TypeArguments.Any(t => t.TypeKind == TypeKind.TypeParameter)) - { - return null; - } - - // Check if this type has any static properties with data source attributes - var hasStaticPropertiesWithDataSources = GetStaticPropertyDataSources(typeSymbol).Any(); - - return hasStaticPropertiesWithDataSources ? typeSymbol : null; - } - - private static void GenerateStaticPropertyInitialization(SourceProductionContext context, ImmutableArray testClasses) - { - if (testClasses.IsEmpty) - { - return; + return EquatableArray.Empty; } // Use a dictionary to deduplicate static properties by their declaring type and name // This prevents duplicate initialization when derived classes inherit static properties var uniqueStaticProperties = new Dictionary<(INamedTypeSymbol DeclaringType, string Name), PropertyWithDataSource>(SymbolEqualityComparer.Default.ToTupleComparer()); + var visitedTypes = new HashSet(SymbolEqualityComparer.Default); + var properties = new List(); - foreach (var testClass in testClasses) + foreach (var context in classesContext) { - var properties = GetStaticPropertyDataSources(testClass); - foreach (var prop in properties) + if (context.SemanticModel.GetDeclaredSymbol(context.Node) is not INamedTypeSymbol typeSymbol) + { + continue; + } + + // Skip open generic types - we can't generate code for types with unbound type parameters + // The initialization will happen in the consuming assembly that provides concrete type arguments + if (typeSymbol.IsGenericType && typeSymbol.TypeArguments.Any(t => t.TypeKind == TypeKind.TypeParameter)) + { + continue; + } + + // Check if this type has any static properties with data source attributes + foreach (var prop in GetStaticPropertyDataSources(typeSymbol, visitedTypes, properties)) { // Static properties belong to their declaring type, not derived types // Only add if we haven't seen this exact property before @@ -88,18 +77,117 @@ private static void GenerateStaticPropertyInitialization(SourceProductionContext } } - var allStaticProperties = uniqueStaticProperties.Values.ToImmutableArray(); + return uniqueStaticProperties.Values.Select(ToPropertyWithDataSourceModel).ToEquatableArray(); + } + + private static List GetStaticPropertyDataSources( + INamedTypeSymbol typeSymbol, + HashSet visitedType, + List properties + ) + { + properties.Clear(); + + // Walk inheritance hierarchy to include base class static properties + var currentType = typeSymbol; + while (currentType != null) + { + if (!visitedType.Add(currentType)) + { + break; + } + + foreach (var member in currentType.GetMembers()) + { + if (member is IPropertySymbol { DeclaredAccessibility: Accessibility.Public, SetMethod.DeclaredAccessibility: Accessibility.Public, IsStatic: true } property) // Only static properties for session initialization + { + var dataSourceAttr = property.GetAttributes() + .FirstOrDefault(a => DataSourceAttributeHelper.IsDataSourceAttribute(a.AttributeClass)); + + if (dataSourceAttr != null) + { + // Check if we already have this property (in case of overrides) + bool newProperty = true; + foreach (var p in properties) + { + if (p.Property.Name == property.Name) + { + newProperty = false; + break; + } + } + + if (newProperty) + { + properties.Add(new PropertyWithDataSource + { + Property = property, + DataSourceAttribute = dataSourceAttr + }); + } + } + } + } + currentType = currentType.BaseType; + } + + return properties; + } + + private static PropertyWithDataSourceModel ToPropertyWithDataSourceModel(PropertyWithDataSource staticProperty) + { + var containingType = new ContainingType( + staticProperty.Property.ContainingType.GloballyQualified(), + staticProperty.Property.ContainingType.Name, + staticProperty.Property.ContainingType.ContainingNamespace?.ToDisplayString() ?? string.Empty, + staticProperty.Property.ContainingType.ContainingAssembly.Name + ); + + var property = new PropertyType( + staticProperty.Property.Type.GloballyQualified(), + staticProperty.Property.Name, + containingType + ); + + var attr = staticProperty.DataSourceAttribute; + var attributeClassName = attr.AttributeClass?.Name; + + DataSourceAttribute sourceAttribute; + + // Generate data source logic based on attribute type + if (attributeClassName == "ArgumentsAttribute") + { + sourceAttribute = ParseArgumentsDataSourceWithAssignment(attr);; + } + else if (attributeClassName == "MethodDataSourceAttribute") + { + sourceAttribute = ParseMethodDataSourceWithAssignment(attr, staticProperty.Property.ContainingType); + } + else if (attr.AttributeClass?.IsOrInherits("global::TUnit.Core.AsyncDataSourceGeneratorAttribute") == true || + attr.AttributeClass?.IsOrInherits("global::TUnit.Core.AsyncUntypedDataSourceGeneratorAttribute") == true) + { + sourceAttribute = new DataSourceAttribute.AsyncDataSource(CodeGenerationHelpers.GenerateAttributeInstantiation(attr)); + } + else + { + sourceAttribute = new DataSourceAttribute.Fallback(); + } + + return new PropertyWithDataSourceModel(property, sourceAttribute); + } - if (allStaticProperties.IsEmpty) + private static void GenerateStaticPropertyInitialization(SourceProductionContext context, EquatableArray testClasses) + { + if (testClasses.Length == 0) { return; } - var code = GenerateInitializationCode(allStaticProperties); + var code = GenerateInitializationCode(testClasses); context.AddSource("StaticPropertyInitializer.g.cs", SourceText.From(code, Encoding.UTF8)); } - private static string GenerateInitializationCode(ImmutableArray staticProperties) + private static string GenerateInitializationCode(EquatableArray staticProperties) { using var writer = new CodeWriter(); writer.AppendLine("using System;"); @@ -128,14 +216,14 @@ private static string GenerateInitializationCode(ImmutableArray new global::TUnit.Core.AssemblyMetadata {{ Name = \"{propertyData.Property.ContainingType.ContainingAssembly.Name}\" }}),"); + writer.AppendLine($"Assembly = global::TUnit.Core.AssemblyMetadata.GetOrAdd(\"{propertyData.Property.ContainingType.ContainingAssemblyName}\", () => new global::TUnit.Core.AssemblyMetadata {{ Name = \"{propertyData.Property.ContainingType.ContainingAssemblyName}\" }}),"); writer.AppendLine("Properties = global::System.Array.Empty(),"); writer.AppendLine("Parameters = global::System.Array.Empty(),"); writer.AppendLine("Parent = null"); @@ -201,7 +289,7 @@ private static void GenerateIndividualPropertyInitializer(CodeWriter writer, Pro writer.AppendLine("{"); writer.Indent(); writer.AppendLine($"Name = \"{propertyName}\","); - writer.AppendLine($"Type = typeof({propertyData.Property.Type.GloballyQualified()}),"); + writer.AppendLine($"Type = typeof({propertyData.Property.GloballyQualifiedType}),"); writer.AppendLine($"IsStatic = true,"); writer.AppendLine($"ReflectionInfo = typeof({typeName}).GetProperty(\"{propertyName}\", global::System.Reflection.BindingFlags.Public | global::System.Reflection.BindingFlags.Static),"); writer.AppendLine($"Getter = _ => {typeName}.{propertyName},"); @@ -212,25 +300,30 @@ private static void GenerateIndividualPropertyInitializer(CodeWriter writer, Pro writer.AppendLine(); var attr = propertyData.DataSourceAttribute; - var attributeClassName = attr.AttributeClass?.Name; // Generate data source logic and capture the value writer.AppendLine("object? value = null;"); writer.AppendLine(); // Generate data source logic based on attribute type - if (attributeClassName == "ArgumentsAttribute") + if (attr is DataSourceAttribute.ArgumentsDataSource argumentsDataSource) { - GenerateArgumentsDataSourceWithAssignment(writer, attr); + if (argumentsDataSource.FormattedValue is not null) + { + writer.AppendLine($"value = {argumentsDataSource.FormattedValue};"); + } } - else if (attributeClassName == "MethodDataSourceAttribute") + else if (attr is DataSourceAttribute.MethodDataSource methodDataSource) { - GenerateMethodDataSourceWithAssignment(writer, attr, propertyData.Property.ContainingType); + if (methodDataSource.Data is not null) + { + writer.AppendLine($"var data = {methodDataSource.Data}();"); + writer.AppendLine("value = await global::TUnit.Core.Helpers.DataSourceHelpers.ProcessDataSourceResultGeneric(data);"); + } } - else if (attr.AttributeClass?.IsOrInherits("global::TUnit.Core.AsyncDataSourceGeneratorAttribute") == true || - attr.AttributeClass?.IsOrInherits("global::TUnit.Core.AsyncUntypedDataSourceGeneratorAttribute") == true) + else if (attr is DataSourceAttribute.AsyncDataSource asyncDataSource) { - GenerateAsyncDataSourceGeneratorWithPropertyWithAssignment(writer, attr); + GenerateAsyncDataSourceGeneratorWithPropertyWithAssignment(writer, asyncDataSource); } else { @@ -242,7 +335,7 @@ private static void GenerateIndividualPropertyInitializer(CodeWriter writer, Pro writer.AppendLine("if (value != null)"); writer.AppendLine("{"); writer.Indent(); - writer.AppendLine($"{typeName}.{propertyName} = ({propertyData.Property.Type.GloballyQualified()})value;"); + writer.AppendLine($"{typeName}.{propertyName} = ({propertyData.Property.GloballyQualifiedType})value;"); writer.Unindent(); writer.AppendLine("}"); writer.AppendLine(); @@ -254,7 +347,7 @@ private static void GenerateIndividualPropertyInitializer(CodeWriter writer, Pro private static readonly TypedConstantFormatter _formatter = new(); - private static void GenerateArgumentsDataSourceWithAssignment(CodeWriter writer, AttributeData attr) + private static DataSourceAttribute ParseArgumentsDataSourceWithAssignment(AttributeData attr) { if (attr.ConstructorArguments.Length > 0) { @@ -266,16 +359,18 @@ private static void GenerateArgumentsDataSourceWithAssignment(CodeWriter writer, // For static property injection, we only use the first value from the array var firstValue = argValue.Values[0]; var formattedValue = _formatter.FormatForCode(firstValue); - writer.AppendLine($"value = {formattedValue};"); + return new DataSourceAttribute.ArgumentsDataSource(formattedValue); } } + + return new DataSourceAttribute.ArgumentsDataSource(null); } - private static void GenerateMethodDataSourceWithAssignment(CodeWriter writer, AttributeData attr, INamedTypeSymbol containingType) + private static DataSourceAttribute ParseMethodDataSourceWithAssignment(AttributeData attr, INamedTypeSymbol containingType) { if (attr.ConstructorArguments.Length < 1) { - return; + return new DataSourceAttribute.MethodDataSource(null); } string? methodName = null; @@ -297,19 +392,16 @@ private static void GenerateMethodDataSourceWithAssignment(CodeWriter writer, At if (string.IsNullOrEmpty(methodName) || targetType == null) { - return; + return new DataSourceAttribute.MethodDataSource(null); } var fullyQualifiedType = targetType.GloballyQualified(); - writer.AppendLine($"var data = {fullyQualifiedType}.{methodName}();"); - writer.AppendLine("value = await global::TUnit.Core.Helpers.DataSourceHelpers.ProcessDataSourceResultGeneric(data);"); + return new DataSourceAttribute.MethodDataSource($"{fullyQualifiedType}.{methodName}"); } - - private static void GenerateAsyncDataSourceGeneratorWithPropertyWithAssignment(CodeWriter writer, AttributeData attr) + private static void GenerateAsyncDataSourceGeneratorWithPropertyWithAssignment(CodeWriter writer, DataSourceAttribute.AsyncDataSource attr) { - var generatorCode = CodeGenerationHelpers.GenerateAttributeInstantiation(attr); - writer.AppendLine($"var generator = {generatorCode};"); + writer.AppendLine($"var generator = {attr.GeneratedCode};"); writer.AppendLine("// Use the global static property context for disposal tracking"); writer.AppendLine("var globalContext = global::TUnit.Core.TestSessionContext.GlobalStaticPropertyContext;"); writer.AppendLine("var metadata = new global::TUnit.Core.DataGeneratorMetadata"); @@ -339,40 +431,4 @@ private static void GenerateAsyncDataSourceGeneratorWithPropertyWithAssignment(C writer.Unindent(); writer.AppendLine("}"); } - - private static ImmutableArray GetStaticPropertyDataSources(INamedTypeSymbol typeSymbol) - { - var properties = new List(); - - // Walk inheritance hierarchy to include base class static properties - var currentType = typeSymbol; - while (currentType != null) - { - foreach (var member in currentType.GetMembers()) - { - if (member is IPropertySymbol { DeclaredAccessibility: Accessibility.Public, SetMethod.DeclaredAccessibility: Accessibility.Public, IsStatic: true } property) // Only static properties for session initialization - { - var dataSourceAttr = property.GetAttributes() - .FirstOrDefault(a => DataSourceAttributeHelper.IsDataSourceAttribute(a.AttributeClass)); - - if (dataSourceAttr != null) - { - // Check if we already have this property (in case of overrides) - if (!properties.Any(p => p.Property.Name == property.Name)) - { - properties.Add(new PropertyWithDataSource - { - Property = property, - DataSourceAttribute = dataSourceAttr - }); - } - } - } - } - currentType = currentType.BaseType; - } - - return properties.ToImmutableArray(); - } - } diff --git a/TUnit.Core.SourceGenerator/Models/PropertyWithDataSource.cs b/TUnit.Core.SourceGenerator/Models/PropertyWithDataSource.cs index d04e9ef9b9..2919387414 100644 --- a/TUnit.Core.SourceGenerator/Models/PropertyWithDataSource.cs +++ b/TUnit.Core.SourceGenerator/Models/PropertyWithDataSource.cs @@ -2,8 +2,22 @@ namespace TUnit.Core.SourceGenerator.Models; -public struct PropertyWithDataSource +public record PropertyWithDataSource { - public IPropertySymbol Property { get; init; } - public AttributeData DataSourceAttribute { get; init; } -} \ No newline at end of file + public required IPropertySymbol Property { get; init; } + public required AttributeData DataSourceAttribute { get; init; } +} + +public record PropertyWithDataSourceModel(PropertyType Property, DataSourceAttribute DataSourceAttribute); + +public record ContainingType(string GloballyQualified, string Name, string ContainingNamespace, string ContainingAssemblyName); + +public record PropertyType(string GloballyQualifiedType, string Name, ContainingType ContainingType); + +public abstract record DataSourceAttribute +{ + public record ArgumentsDataSource(string? FormattedValue) : DataSourceAttribute; + public record MethodDataSource(string? Data) : DataSourceAttribute; + public record AsyncDataSource(string GeneratedCode) : DataSourceAttribute; + public record Fallback : DataSourceAttribute; +} diff --git a/TUnit.SourceGenerator.IncrementalTests/AotConverterGeneratorIncrementalTests.cs b/TUnit.SourceGenerator.IncrementalTests/AotConverterGeneratorIncrementalTests.cs index d70cf5c403..6c8669eb45 100644 --- a/TUnit.SourceGenerator.IncrementalTests/AotConverterGeneratorIncrementalTests.cs +++ b/TUnit.SourceGenerator.IncrementalTests/AotConverterGeneratorIncrementalTests.cs @@ -111,7 +111,7 @@ private static void AssertRunReasons( var runResult = driver.GetRunResult().Results[0]; var runValue = runResult.TrackedSteps[AotConverterGenerator.ParseAotConverter][0].Outputs[0].Value; var runState = (ValueTuple, bool>)runValue; - Assert.That(runState.Item1.Length == conversionMetadataLength); + Xunit.Assert.Equal(conversionMetadataLength, runState.Item1.Length); TestHelper.AssertRunReason(runResult, AotConverterGenerator.ParseAotConverter, reasons.BuildStep, outputIndex); } diff --git a/TUnit.SourceGenerator.IncrementalTests/StaticPropertyInitializationGeneratorIncrementalTests.cs b/TUnit.SourceGenerator.IncrementalTests/StaticPropertyInitializationGeneratorIncrementalTests.cs new file mode 100644 index 0000000000..14108149c8 --- /dev/null +++ b/TUnit.SourceGenerator.IncrementalTests/StaticPropertyInitializationGeneratorIncrementalTests.cs @@ -0,0 +1,117 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using TUnit.Core.SourceGenerator.CodeGenerators; +using TUnit.Core.SourceGenerator.Models; + +namespace TUnit.Assertions.SourceGenerator.IncrementalTests; + +public class StaticPropertyInitializationGeneratorIncrementalTests +{ + private const string DefaultProperties = + """ + #nullable enabled + using System.ComponentModel; + using TUnit.Assertions.Attributes; + using TUnit.Core; + + public class StaticPropertyDataSourceTests + { + // Static property with Arguments attribute + [Arguments("static injected value")] + public static string? StaticStringProperty { get; set; } + + // Static property with MethodDataSource + [MethodDataSource(nameof(GetStaticTestData))] + public static string? StaticDataProperty { get; set; } + + public static IStaticTestDataProvider? StaticDataProviderProperty { get; set; } + } + """; + + [Fact] + public void AddUnrelatedMethodShouldNotRegenerate() + { + var syntaxTree = CSharpSyntaxTree.ParseText(DefaultProperties, CSharpParseOptions.Default); + var compilation1 = Fixture.CreateLibrary(syntaxTree); + + var driver1 = TestHelper.GenerateTracked(compilation1); + AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); + + var compilation2 = compilation1.AddSyntaxTrees(CSharpSyntaxTree.ParseText("struct MyValue {}")); + var driver2 = driver1.RunGenerators(compilation2); + AssertRunParseLength(driver2,2); + AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Unchanged); + } + + [Fact] + public void AddClassWithValidPropertyShouldRegenerate() + { + var syntaxTree = CSharpSyntaxTree.ParseText(DefaultProperties, CSharpParseOptions.Default); + var compilation1 = Fixture.CreateLibrary(syntaxTree); + + var driver1 = TestHelper.GenerateTracked(compilation1); + AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); + + var compilation2 = compilation1.AddSyntaxTrees(CSharpSyntaxTree.ParseText( + """ + using TUnit.Assertions.Attributes; + using TUnit.Core; + + public class ExtraStaticProperty + { + // Static property with ClassDataSource + [ClassDataSource] + public static IStaticTestDataProvider? StaticDataProviderProperty { get; set; } + } + """)); + var driver2 = driver1.RunGenerators(compilation2); + AssertRunParseLength(driver2,3); + AssertRunReasons(driver2, IncrementalGeneratorRunReasons.ModifiedSource); + } + + [Fact] + public void ModifyPropertyAttributeShouldRegenerate() + { + var syntaxTree = CSharpSyntaxTree.ParseText(DefaultProperties, CSharpParseOptions.Default); + var compilation1 = Fixture.CreateLibrary(syntaxTree); + + var driver1 = TestHelper.GenerateTracked(compilation1); + AssertRunReasons(driver1, IncrementalGeneratorRunReasons.New); + + var compilation2 = compilation1.AddSyntaxTrees(CSharpSyntaxTree.ParseText( + """ + using TUnit.Assertions.Attributes; + using TUnit.Core; + + public class ExtraStaticProperty + { + // Static property with ClassDataSource + [ClassDataSource] + public static IStaticTestDataProvider? StaticDataProviderProperty { get; set; } + } + """)); + var driver2 = driver1.RunGenerators(compilation2); + AssertRunReasons(driver2, IncrementalGeneratorRunReasons.Modified); + } + + private static void AssertRunReasons( + GeneratorDriver driver, + IncrementalGeneratorRunReasons reasons, + int outputIndex = 0 + ) + { + var runResult = driver.GetRunResult().Results[0]; + TestHelper.AssertRunReason(runResult, StaticPropertyInitializationGenerator.ParseStaticProperties, reasons.BuildStep, outputIndex); + } + + private static void AssertRunParseLength( + GeneratorDriver driver, + int staticPropertyModelCount + ) + { + var runResult = driver.GetRunResult().Results[0]; + var runValue = runResult.TrackedSteps[StaticPropertyInitializationGenerator.ParseStaticProperties][0].Outputs[0].Value; + var runState = (EquatableArray)runValue; + Xunit.Assert.Equal(staticPropertyModelCount, runState.Length); + } +} diff --git a/TUnit.SourceGenerator.IncrementalTests/TestHelper.cs b/TUnit.SourceGenerator.IncrementalTests/TestHelper.cs index 4490b79a1b..442108fb3a 100644 --- a/TUnit.SourceGenerator.IncrementalTests/TestHelper.cs +++ b/TUnit.SourceGenerator.IncrementalTests/TestHelper.cs @@ -85,6 +85,27 @@ string newDeclaration return compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), newTree); } + internal static CSharpCompilation ReplacePropertyDeclaration( + CSharpCompilation compilation, + string propertyName, + string newDeclaration + ) + { + var syntaxTree = compilation.SyntaxTrees.Single(); + + var memberDeclaration = syntaxTree + .GetCompilationUnitRoot() + .DescendantNodes() + .OfType() + .First(x => x.Identifier.Text == propertyName); + var updatedMemberDeclaration = SyntaxFactory.ParseMemberDeclaration(newDeclaration)!; + + var newRoot = syntaxTree.GetCompilationUnitRoot().ReplaceNode(memberDeclaration, updatedMemberDeclaration); + var newTree = syntaxTree.WithRootAndOptions(newRoot, syntaxTree.Options); + + return compilation.ReplaceSyntaxTree(compilation.SyntaxTrees.First(), newTree); + } + public static void AssertRunReason( GeneratorRunResult runResult, string stepName, @@ -122,6 +143,11 @@ IncrementalStepRunReason ReportDiagnosticsStep IncrementalStepRunReason.Cached ); + public static readonly IncrementalGeneratorRunReasons Unchanged = new( + IncrementalStepRunReason.Unchanged, + IncrementalStepRunReason.Cached + ); + public static readonly IncrementalGeneratorRunReasons Modified = Cached with { ReportDiagnosticsStep = IncrementalStepRunReason.Modified,