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/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);
+ }
}
});
}
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();
+ }
}