From 3c3b9c3dc7f06de9ffd9d29d4c3bd12919f2bce0 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Wed, 24 Dec 2025 16:39:13 +0000 Subject: [PATCH 1/2] Improve AlsoNotify handling in ReactiveGenerator Refactors the logic for extracting AlsoNotify property names from [Reactive] attributes to be more robust, handling various constructor argument forms and fallback scenarios. Adds validation in test helpers to ensure generated code includes property change notifications for all AlsoNotify properties specified via nameof(). Updates test baselines to reflect improved code generation. --- ...ify#TestNs.TestVM.Properties.g.verified.cs | 4 +- ...tes#TestNs.TestVM.Properties.g.verified.cs | 3 +- ...ies#TestNs.TestVM.Properties.g.verified.cs | 3 +- ...lue#TestNs.TestVM.Properties.g.verified.cs | 3 +- ...ess#TestNs.TestVM.Properties.g.verified.cs | 3 +- ...nce#TestNs.TestVM.Properties.g.verified.cs | 3 +- ...ss#TestNs1.TestVM.Properties.g.verified.cs | 3 +- ...ss#TestNs2.TestVM.Properties.g.verified.cs | 3 +- ...nit#TestNs.TestVM.Properties.g.verified.cs | 5 +- ...3+TestInnerClass1.Properties.g.verified.cs | 3 +- ...2+TestInnerClass3.Properties.g.verified.cs | 3 +- ...3+TestInnerClass2.Properties.g.verified.cs | 3 +- ...s1.TestViewModel3.Properties.g.verified.cs | 5 +- .../TestHelper.cs | 84 +++++++++++++++- .../Reactive/ReactiveGenerator.Execute.cs | 98 ++++++++++++++----- 15 files changed, 171 insertions(+), 55 deletions(-) diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveFromPartialWithAlsoNotify#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveFromPartialWithAlsoNotify#TestNs.TestVM.Properties.g.verified.cs index 36376f8..76474e5 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveFromPartialWithAlsoNotify#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveFromPartialWithAlsoNotify#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// @@ -20,6 +19,7 @@ public int Test4 set { this.RaiseAndSetIfChanged(ref _test4, value); + this.RaisePropertyChanged(nameof(OtherNotifyProperty)); } } } diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#TestNs.TestVM.Properties.g.verified.cs index 8a2e02d..651537e 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperiesWithAttributes#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#TestNs.TestVM.Properties.g.verified.cs index 55a7f05..af8e019 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactiveProperties#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs index f0515a5..f975a40 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesCalledValue#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#TestNs.TestVM.Properties.g.verified.cs index 03151e4..7903dbf 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAccess#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestNs.TestVM.Properties.g.verified.cs index 6eb33cf..8ff6ccd 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithAttributesAccessAndInheritance#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs1.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs1.TestVM.Properties.g.verified.cs index bb716ed..bc0452d 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs1.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs1.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs1 { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs2.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs2.TestVM.Properties.g.verified.cs index 75dc500..4ef72d3 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs2.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithIdenticalClass#TestNs2.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs2 { - - public partial class TestVM + public partial class TestVM { /// diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#TestNs.TestVM.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#TestNs.TestVM.Properties.g.verified.cs index 1514954..7914df1 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#TestNs.TestVM.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithInit#TestNs.TestVM.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs { - - public partial class TestVM + public partial class TestVM { /// @@ -25,4 +24,4 @@ public string MustBeSet } } #nullable restore -#pragma warning restore \ No newline at end of file +#pragma warning restore diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass1.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass1.Properties.g.verified.cs index 68d382c..781e770 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass1.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass1.Properties.g.verified.cs @@ -9,7 +9,6 @@ namespace TestNs1 { public partial class TestViewModel3 { - public partial class TestInnerClass1 { @@ -41,4 +40,4 @@ public int TestInner11 } #nullable restore -#pragma warning restore \ No newline at end of file +#pragma warning restore diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2+TestInnerClass3.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2+TestInnerClass3.Properties.g.verified.cs index 64e8205..db2e26a 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2+TestInnerClass3.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2+TestInnerClass3.Properties.g.verified.cs @@ -11,7 +11,6 @@ public partial class TestViewModel3 { public partial class TestInnerClass2 { - public partial class TestInnerClass3 { @@ -44,4 +43,4 @@ public int TestInner33 } #nullable restore -#pragma warning restore \ No newline at end of file +#pragma warning restore diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2.Properties.g.verified.cs index 82b2dcb..e65a395 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3+TestInnerClass2.Properties.g.verified.cs @@ -9,7 +9,6 @@ namespace TestNs1 { public partial class TestViewModel3 { - public partial class TestInnerClass2 { @@ -41,4 +40,4 @@ public int TestInner22 } #nullable restore -#pragma warning restore \ No newline at end of file +#pragma warning restore diff --git a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3.Properties.g.verified.cs b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3.Properties.g.verified.cs index b8e54c8..4c8c205 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3.Properties.g.verified.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/REACTIVE/ReactiveGeneratorTests.FromReactivePropertiesWithNestedClass#TestNs1.TestViewModel3.Properties.g.verified.cs @@ -7,8 +7,7 @@ namespace TestNs1 { - - public partial class TestViewModel3 + public partial class TestViewModel3 { /// @@ -37,4 +36,4 @@ public float TestVM3Property2 } } #nullable restore -#pragma warning restore \ No newline at end of file +#pragma warning restore diff --git a/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs b/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs index 6f471a3..c880eb1 100644 --- a/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs +++ b/src/ReactiveUI.SourceGenerator.Tests/TestHelper.cs @@ -3,6 +3,7 @@ // The ReactiveUI and contributors licenses this file to you under the MIT license. // See the LICENSE file in the project root for full license information. +using System.Text.RegularExpressions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; @@ -26,8 +27,8 @@ namespace ReactiveUI.SourceGenerator.Tests; /// It provides utilities to initialize dependencies, run generators, and verify the output. /// /// Type of Incremental Generator. -/// -public sealed class TestHelper : IDisposable +/// +public sealed partial class TestHelper : IDisposable where T : IIncrementalGenerator, new() { /// @@ -209,7 +210,7 @@ public SettingsTask RunGeneratorAndCheck( if (rerunCompilation) { // Run the generator and capture diagnostics. - var rerunDriver = driver.RunGeneratorsAndUpdateCompilation(compilation, out _, out var diagnostics); + var rerunDriver = driver.RunGeneratorsAndUpdateCompilation(compilation, out var outputCompilation, out var diagnostics); // If any warnings or errors are found, log them to the test output before throwing an exception. var offendingDiagnostics = diagnostics @@ -226,6 +227,9 @@ public SettingsTask RunGeneratorAndCheck( throw new InvalidOperationException("Compilation failed due to the above diagnostics."); } + // Validate generated code contains expected features + ValidateGeneratedCode(code, rerunDriver); + return VerifyGenerator(rerunDriver); } @@ -233,5 +237,79 @@ public SettingsTask RunGeneratorAndCheck( return VerifyGenerator(driver.RunGenerators(compilation)); } + [GeneratedRegex(@"\[Reactive\((?:.*?nameof\((\w+)\))+", RegexOptions.Singleline)] + private static partial Regex ReactiveRegex(); + + [GeneratedRegex(@"nameof\((\w+)\)")] + private static partial Regex NameOfRegex(); + + /// + /// Validates that generated code contains expected features based on the source code attributes. + /// + /// The original source code. + /// The generator driver with generated output. + private static void ValidateGeneratedCode(string sourceCode, GeneratorDriver driver) + { + var runResult = driver.GetRunResult(); + var generatedTrees = runResult.Results.SelectMany(r => r.GeneratedSources).ToList(); + var allGeneratedCode = string.Join("\n", generatedTrees.Select(t => t.SourceText.ToString())); + + // Check for AlsoNotify feature in Reactive attributes + // Pattern matches: [Reactive(nameof(PropertyName))] or [Reactive(nameof(Prop1), nameof(Prop2))] + var alsoNotifyPattern = ReactiveRegex(); + var nameofPattern = NameOfRegex(); + var matches = alsoNotifyPattern.Matches(sourceCode); + + TestContext.Out.WriteLine("=== VALIDATION DEBUG ==="); + TestContext.Out.WriteLine("Found {0} Reactive attributes with nameof", matches.Count); + + if (matches.Count > 0) + { + foreach (Match match in matches) + { + TestContext.Out.WriteLine("Checking attribute: {0}", match.Value); + + // Extract all nameof() references within this attribute + var nameofMatches = nameofPattern.Matches(match.Value); + TestContext.Out.WriteLine("Found {0} nameof references in this attribute", nameofMatches.Count); + + foreach (Match nameofMatch in nameofMatches) + { + var propertyToNotify = nameofMatch.Groups[1].Value; + TestContext.Out.WriteLine("Checking for notification of property: {0}", propertyToNotify); + + // Verify that the generated code contains calls to raise property changed for the additional property + // Check for various forms of property change notification + var hasNotification = + allGeneratedCode.Contains($"this.RaisePropertyChanged(nameof({propertyToNotify}))") || + allGeneratedCode.Contains($"this.RaisePropertyChanged(\"{propertyToNotify}\")") || + allGeneratedCode.Contains($"RaisePropertyChanged(nameof({propertyToNotify}))") || + allGeneratedCode.Contains($"RaisePropertyChanged(\"{propertyToNotify}\")"); + + TestContext.Out.WriteLine("Has notification: {0}", hasNotification); + + if (!hasNotification) + { + var errorMessage = $"Generated code does not include AlsoNotify for property '{propertyToNotify}'. " + + $"Expected to find property change notification for '{propertyToNotify}' in the generated code.\n" + + $"Source attribute: {match.Value}"; + + TestContext.Out.WriteLine("=== VALIDATION FAILURE ==="); + TestContext.Out.WriteLine(errorMessage); + TestContext.Out.WriteLine("=== SOURCE CODE SNIPPET ==="); + TestContext.Out.WriteLine(match.Value); + TestContext.Out.WriteLine("=== GENERATED CODE ==="); + TestContext.Out.WriteLine(allGeneratedCode); + TestContext.Out.WriteLine("=== END ==="); + + throw new InvalidOperationException(errorMessage); + } + } + } + } + + TestContext.Out.WriteLine("=== END VALIDATION DEBUG ==="); + } + private SettingsTask VerifyGenerator(GeneratorDriver driver) => Verify(driver).UseDirectory(VerifiedFilePath()).ScrubLinesContaining("[global::System.CodeDom.Compiler.GeneratedCode(\""); } diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs index 568399e..3387eb1 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/Reactive/ReactiveGenerator.Execute.cs @@ -4,7 +4,6 @@ // See the LICENSE file in the project root for full license information. using System; -using System.Collections.Immutable; using System.Linq; using System.Threading; using Microsoft.CodeAnalysis; @@ -149,16 +148,7 @@ public sealed partial class ReactiveGenerator token.ThrowIfCancellationRequested(); - var alsoNotify = attributeData.GetConstructorArguments() - .Where(notify => !string.IsNullOrEmpty(notify) && !string.Equals(notify, propertyName, StringComparison.Ordinal)); - using var alsoNotifyBuilder = ImmutableArrayBuilder.Rent(); - if (alsoNotify is not null) - { - foreach (var notify in alsoNotify) - { - alsoNotifyBuilder.Add(notify!); - } - } + var alsoNotify = GetAlsoNotifyValues(attributeData, propertyName, context.SemanticModel, token); token.ThrowIfCancellationRequested(); @@ -181,7 +171,7 @@ public sealed partial class ReactiveGenerator useRequired, true, propertyAccessModifier!, - alsoNotifyBuilder.ToImmutable()), + alsoNotify), builder.ToImmutable()); } #endif @@ -289,16 +279,7 @@ public sealed partial class ReactiveGenerator token.ThrowIfCancellationRequested(); - var alsoNotify = attributeData.GetConstructorArguments() - .Where(notify => !string.IsNullOrEmpty(notify) && !string.Equals(notify, propertyName, StringComparison.Ordinal)); - using var alsoNotifyBuilder = ImmutableArrayBuilder.Rent(); - if (alsoNotify is not null) - { - foreach (var notify in alsoNotify) - { - alsoNotifyBuilder.Add(notify!); - } - } + var alsoNotify = GetAlsoNotifyValues(attributeData, propertyName, context.SemanticModel, token); token.ThrowIfCancellationRequested(); @@ -321,7 +302,7 @@ public sealed partial class ReactiveGenerator useRequired, false, "public", - alsoNotifyBuilder.ToImmutable()), + alsoNotify), builder.ToImmutable()); } @@ -374,7 +355,6 @@ private static string GenerateClassWithProperties(string containingTypeName, str return $$""" - {{containingClassVisibility}} partial {{containingType}} {{containingTypeName}} { {{propertyDeclarations}} @@ -469,4 +449,74 @@ private static string GetPropertySyntax(PropertyInfo propertyInfo) } """; } + + private static EquatableArray GetAlsoNotifyValues(AttributeData attributeData, string propertyName, SemanticModel semanticModel, CancellationToken token) + { + using var builder = ImmutableArrayBuilder.Rent(); + + // Prefer the helper that flattens params arrays reliably + foreach (var notify in attributeData.GetConstructorArguments()) + { + if (string.IsNullOrWhiteSpace(notify) || string.Equals(notify, propertyName, StringComparison.Ordinal)) + { + continue; + } + + builder.Add(notify!); + } + + // Fallback for safety with any remaining constructor arguments + foreach (var argument in attributeData.ConstructorArguments) + { + if (argument.Kind == TypedConstantKind.Array) + { + if (argument.Values.IsDefaultOrEmpty) + { + continue; + } + + foreach (var value in argument.Values) + { + if (value.Value is string notifyValue && + !string.IsNullOrWhiteSpace(notifyValue) && + !string.Equals(notifyValue, propertyName, StringComparison.Ordinal)) + { + builder.Add(notifyValue); + } + } + } + else if (argument.Value is string notifyValue && + !string.IsNullOrWhiteSpace(notifyValue) && + !string.Equals(notifyValue, propertyName, StringComparison.Ordinal)) + { + builder.Add(notifyValue); + } + } + + // If nothing was resolved, try reading the syntax and semantic model directly + if (builder.Count == 0 && attributeData.ApplicationSyntaxReference?.GetSyntax(token) is AttributeSyntax attributeSyntax) + { + var arguments = attributeSyntax.ArgumentList?.Arguments; + if (arguments is not null) + { + foreach (var argument in arguments.Value) + { + var constantValue = semanticModel.GetConstantValue(argument.Expression, token); + if (!constantValue.HasValue || constantValue.Value is not string notifyValue) + { + continue; + } + + if (string.IsNullOrWhiteSpace(notifyValue) || string.Equals(notifyValue, propertyName, StringComparison.Ordinal)) + { + continue; + } + + builder.Add(notifyValue); + } + } + } + + return builder.ToImmutable(); + } } From 4e331157f66794dc0c6580f0801f35ff081e50f9 Mon Sep 17 00:00:00 2001 From: Chris Pulman Date: Wed, 24 Dec 2025 17:34:25 +0000 Subject: [PATCH 2/2] Prevent empty source files in IViewForGenerator Adds a check to only generate source files when a supported UI framework base type is detected, avoiding creation of empty generated files. --- .../IViewFor/IViewForGenerator.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs index 1bb994e..0e2d342 100644 --- a/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs +++ b/src/ReactiveUI.SourceGenerators.Roslyn/IViewFor/IViewForGenerator.cs @@ -68,7 +68,12 @@ public void Initialize(IncrementalGeneratorInitializationContext context) } var source = GenerateSource(grouping.Key.TargetName, grouping.Key.TargetNamespace, grouping.Key.TargetVisibility, grouping.Key.TargetType, grouping.FirstOrDefault()); - context.AddSource(grouping.Key.FileHintName + ".IViewFor.g.cs", source); + + // Only add source if it's not empty (i.e., a supported UI framework base type was detected) + if (!string.IsNullOrWhiteSpace(source)) + { + context.AddSource(grouping.Key.FileHintName + ".IViewFor.g.cs", source); + } } }); }