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