diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
index c4cbb324e..9dc77b7d1 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/CommunityToolkit.Mvvm.SourceGenerators.projitems
@@ -48,7 +48,7 @@
-
+
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs
index 1009cd4c1..74b456063 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/AttributeInfo.cs
@@ -9,6 +9,7 @@
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using CommunityToolkit.Mvvm.SourceGenerators.Helpers;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
@@ -17,10 +18,12 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
///
/// A model representing an attribute declaration.
///
+/// Indicates the target of the attribute.
/// The type name of the attribute.
/// The values for all constructor arguments for the attribute.
/// The values for all named arguments for the attribute.
internal sealed record AttributeInfo(
+ SyntaxKind AttributeTarget,
string TypeName,
EquatableArray ConstructorArgumentInfo,
EquatableArray<(string Name, TypedConstantInfo Value)> NamedArgumentInfo)
@@ -50,6 +53,7 @@ public static AttributeInfo Create(AttributeData attributeData)
}
return new(
+ SyntaxKind.PropertyKeyword,
typeName,
constructorArguments.ToImmutable(),
namedArguments.ToImmutable());
@@ -61,6 +65,7 @@ public static AttributeInfo Create(AttributeData attributeData)
/// The symbol for the attribute type.
/// The instance for the current run.
/// The sequence of instances to process.
+ /// The kind of target for the attribute.
/// The cancellation token for the current operation.
/// The resulting instance, if available
/// Whether a resulting instance could be created.
@@ -68,6 +73,7 @@ public static bool TryCreate(
INamedTypeSymbol typeSymbol,
SemanticModel semanticModel,
IEnumerable arguments,
+ SyntaxKind syntaxKind,
CancellationToken token,
[NotNullWhen(true)] out AttributeInfo? info)
{
@@ -105,6 +111,7 @@ public static bool TryCreate(
}
info = new AttributeInfo(
+ syntaxKind,
typeName,
constructorArguments.ToImmutable(),
namedArguments.ToImmutable());
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
index 52b7f44fe..b127bc7a3 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservablePropertyGenerator.Execute.cs
@@ -216,10 +216,10 @@ public static bool TryGetInfo(
// Gather explicit forwarded attributes info
foreach (AttributeListSyntax attributeList in fieldSyntax.AttributeLists)
{
- // Only look for attribute lists explicitly targeting the (generated) property. Roslyn will normally emit a
- // CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic suppressor
- // that recognizes uses of this target specifically to support [ObservableProperty].
- if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword))
+ // Only look for attribute lists explicitly targeting the (generated) property or one of its accessors. Roslyn will
+ // normally emit a CS0657 warning (invalid target), but that is automatically suppressed by a dedicated diagnostic
+ // suppressor that recognizes uses of this target specifically to support [ObservableProperty].
+ if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword or SyntaxKind.GetKeyword or SyntaxKind.SetKeyword) targetIdentifier)
{
continue;
}
@@ -256,7 +256,7 @@ public static bool TryGetInfo(
IEnumerable attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty();
// Try to extract the forwarded attribute
- if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, token, out AttributeInfo? attributeInfo))
+ if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, targetIdentifier.Kind(), token, out AttributeInfo? attributeInfo))
{
builder.Add(
InvalidPropertyTargetedAttributeExpressionOnObservablePropertyField,
@@ -1025,11 +1025,22 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
Argument(IdentifierName("value")))),
Block(setterStatements.AsEnumerable()));
- // Prepare the forwarded attributes, if any
- ImmutableArray forwardedAttributes =
+ // Prepare the forwarded attributes, if any, for all targets
+ AttributeListSyntax[] forwardedPropertyAttributes =
propertyInfo.ForwardedAttributes
+ .Where(static a => a.AttributeTarget is SyntaxKind.PropertyKeyword)
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
- .ToImmutableArray();
+ .ToArray();
+ AttributeListSyntax[] forwardedGetAccessorAttributes =
+ propertyInfo.ForwardedAttributes
+ .Where(static a => a.AttributeTarget is SyntaxKind.GetKeyword)
+ .Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
+ .ToArray();
+ AttributeListSyntax[] forwardedSetAccessorAttributes =
+ propertyInfo.ForwardedAttributes
+ .Where(static a => a.AttributeTarget is SyntaxKind.SetKeyword)
+ .Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
+ .ToArray();
// Prepare the setter for the generated property:
//
@@ -1065,6 +1076,9 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal("The type of the current instance cannot be statically discovered.")))))));
}
+ // Also add any forwarded attributes
+ setAccessor = setAccessor.AddAttributeLists(forwardedSetAccessorAttributes);
+
// Construct the generated property as follows:
//
// ///
@@ -1073,6 +1087,7 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
//
// public
// {
+ //
// get => ;
//
// }
@@ -1086,12 +1101,13 @@ public static MemberDeclarationSyntax GetPropertySyntax(PropertyInfo propertyInf
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservablePropertyGenerator).Assembly.GetName().Version.ToString()))))))
.WithOpenBracketToken(Token(TriviaList(Comment($"/// ")), SyntaxKind.OpenBracketToken, TriviaList())),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))))
- .AddAttributeLists(forwardedAttributes.ToArray())
+ .AddAttributeLists(forwardedPropertyAttributes)
.AddModifiers(Token(SyntaxKind.PublicKeyword))
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
.WithExpressionBody(ArrowExpressionClause(getterFieldExpression))
- .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)),
+ .WithSemicolonToken(Token(SyntaxKind.SemicolonToken))
+ .AddAttributeLists(forwardedGetAccessorAttributes),
setAccessor);
}
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs
index 4a6c803a9..2f82c8b3a 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/SuppressionDescriptors.cs
@@ -17,7 +17,15 @@ internal static class SuppressionDescriptors
public static readonly SuppressionDescriptor PropertyAttributeListForObservablePropertyField = new(
id: "MVVMTKSPR0001",
suppressedDiagnosticId: "CS0657",
- justification: "Fields using [ObservableProperty] can use [property:] attribute lists to forward attributes to the generated properties");
+ justification: "Fields using [ObservableProperty] can use [property:], [set:] and [set:] attribute lists to forward attributes to the generated properties");
+
+ ///
+ /// Gets a for a field using [ObservableProperty] with an attribute list targeting a get or set accessor.
+ ///
+ public static readonly SuppressionDescriptor PropertyAttributeListForObservablePropertyFieldAccessors = new(
+ id: "MVVMTKSPR0001",
+ suppressedDiagnosticId: "CS0658",
+ justification: "Fields using [ObservableProperty] can use [property:], [set:] and [set:] attribute lists to forward attributes to the generated properties");
///
/// Gets a for a method using [RelayCommand] with an attribute list targeting a field or property.
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithSupportedTargetDiagnosticSuppressor.cs
similarity index 85%
rename from src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs
rename to src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithSupportedTargetDiagnosticSuppressor.cs
index f27f3969a..0b9b4246d 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Suppressors/ObservablePropertyAttributeWithSupportedTargetDiagnosticSuppressor.cs
@@ -14,7 +14,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
///
///
-/// A diagnostic suppressor to suppress CS0657 warnings for fields with [ObservableProperty] using a [property:] attribute list.
+/// A diagnostic suppressor to suppress CS0657 warnings for fields with [ObservableProperty] using a [property:] attribute list (or [set:] or [get:]).
///
///
/// That is, this diagnostic suppressor will suppress the following diagnostic:
@@ -29,10 +29,10 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
///
///
[DiagnosticAnalyzer(LanguageNames.CSharp)]
-public sealed class ObservablePropertyAttributeWithPropertyTargetDiagnosticSuppressor : DiagnosticSuppressor
+public sealed class ObservablePropertyAttributeWithSupportedTargetDiagnosticSuppressor : DiagnosticSuppressor
{
///
- public override ImmutableArray SupportedSuppressions => ImmutableArray.Create(PropertyAttributeListForObservablePropertyField);
+ public override ImmutableArray SupportedSuppressions => ImmutableArray.Create(PropertyAttributeListForObservablePropertyField, PropertyAttributeListForObservablePropertyFieldAccessors);
///
public override void ReportSuppressions(SuppressionAnalysisContext context)
@@ -43,7 +43,7 @@ public override void ReportSuppressions(SuppressionAnalysisContext context)
// Check that the target is effectively [property:] over a field declaration with at least one variable, which is the only case we are interested in
if (syntaxNode is AttributeTargetSpecifierSyntax { Parent.Parent: FieldDeclarationSyntax { Declaration.Variables.Count: > 0 } fieldDeclaration } attributeTarget &&
- attributeTarget.Identifier.IsKind(SyntaxKind.PropertyKeyword))
+ (attributeTarget.Identifier.IsKind(SyntaxKind.PropertyKeyword) || attributeTarget.Identifier.IsKind(SyntaxKind.GetKeyword) || attributeTarget.Identifier.IsKind(SyntaxKind.SetKeyword)))
{
SemanticModel semanticModel = context.GetSemanticModel(syntaxNode.SyntaxTree);
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs
index 16ca48c0a..70372a03f 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/Models/CommandInfo.cs
@@ -23,8 +23,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
/// Whether or not concurrent executions have been enabled.
/// Whether or not exceptions should flow to the task scheduler.
/// Whether or not to also generate a cancel command.
-/// The sequence of forwarded attributes for the generated field.
-/// The sequence of forwarded attributes for the generated property.
+/// The sequence of forwarded attributes for the generated members.
internal sealed record CommandInfo(
string MethodName,
string FieldName,
@@ -39,5 +38,4 @@ internal sealed record CommandInfo(
bool AllowConcurrentExecutions,
bool FlowExceptionsToTaskScheduler,
bool IncludeCancelCommand,
- EquatableArray ForwardedFieldAttributes,
- EquatableArray ForwardedPropertyAttributes);
+ EquatableArray ForwardedAttributes);
diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs
index e5b753160..d40086135 100644
--- a/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs
+++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Input/RelayCommandGenerator.Execute.cs
@@ -141,8 +141,7 @@ public static bool TryGetInfo(
semanticModel,
token,
in builder,
- out ImmutableArray fieldAttributes,
- out ImmutableArray propertyAttributes);
+ out ImmutableArray forwardedAttributes);
token.ThrowIfCancellationRequested();
@@ -160,8 +159,7 @@ public static bool TryGetInfo(
allowConcurrentExecutions,
flowExceptionsToTaskScheduler,
generateCancelCommand,
- fieldAttributes,
- propertyAttributes);
+ forwardedAttributes);
diagnostics = builder.ToImmutable();
@@ -196,16 +194,18 @@ public static ImmutableArray GetSyntax(CommandInfo comm
: $"{commandInfo.DelegateType}<{string.Join(", ", commandInfo.DelegateTypeArguments)}>";
// Prepare the forwarded field attributes, if any
- ImmutableArray forwardedFieldAttributes =
- commandInfo.ForwardedFieldAttributes
+ AttributeListSyntax[] forwardedFieldAttributes =
+ commandInfo.ForwardedAttributes
+ .Where(static a => a.AttributeTarget is SyntaxKind.FieldKeyword)
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
- .ToImmutableArray();
+ .ToArray();
// Also prepare any forwarded property attributes
- ImmutableArray forwardedPropertyAttributes =
- commandInfo.ForwardedPropertyAttributes
+ AttributeListSyntax[] forwardedPropertyAttributes =
+ commandInfo.ForwardedAttributes
+ .Where(static a => a.AttributeTarget is SyntaxKind.PropertyKeyword)
.Select(static a => AttributeList(SingletonSeparatedList(a.GetSyntax())))
- .ToImmutableArray();
+ .ToArray();
// Construct the generated field as follows:
//
@@ -225,7 +225,7 @@ public static ImmutableArray GetSyntax(CommandInfo comm
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(RelayCommandGenerator).FullName))),
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(RelayCommandGenerator).Assembly.GetName().Version.ToString()))))))
.WithOpenBracketToken(Token(TriviaList(Comment($"/// The backing field for .")), SyntaxKind.OpenBracketToken, TriviaList())))
- .AddAttributeLists(forwardedFieldAttributes.ToArray());
+ .AddAttributeLists(forwardedFieldAttributes);
// Prepares the argument to pass the underlying method to invoke
using ImmutableArrayBuilder commandCreationArguments = ImmutableArrayBuilder.Rent();
@@ -332,7 +332,7 @@ public static ImmutableArray GetSyntax(CommandInfo comm
SyntaxKind.OpenBracketToken,
TriviaList())),
AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))))
- .AddAttributeLists(forwardedPropertyAttributes.ToArray())
+ .AddAttributeLists(forwardedPropertyAttributes)
.WithExpressionBody(
ArrowExpressionClause(
AssignmentExpression(
@@ -972,26 +972,22 @@ private static bool TryGetCanExecuteMemberFromGeneratedProperty(
/// The instance for the current run.
/// The cancellation token for the current operation.
/// The current collection of gathered diagnostics.
- /// The resulting field attributes to forward.
- /// The resulting property attributes to forward.
+ /// The resulting attributes to forward.
private static void GatherForwardedAttributes(
IMethodSymbol methodSymbol,
SemanticModel semanticModel,
CancellationToken token,
in ImmutableArrayBuilder diagnostics,
- out ImmutableArray fieldAttributes,
- out ImmutableArray propertyAttributes)
+ out ImmutableArray forwardedAttributes)
{
- using ImmutableArrayBuilder fieldAttributesInfo = ImmutableArrayBuilder.Rent();
- using ImmutableArrayBuilder propertyAttributesInfo = ImmutableArrayBuilder.Rent();
+ using ImmutableArrayBuilder forwardedAttributesInfo = ImmutableArrayBuilder.Rent();
static void GatherForwardedAttributes(
IMethodSymbol methodSymbol,
SemanticModel semanticModel,
CancellationToken token,
in ImmutableArrayBuilder diagnostics,
- in ImmutableArrayBuilder fieldAttributesInfo,
- in ImmutableArrayBuilder propertyAttributesInfo)
+ in ImmutableArrayBuilder forwardedAttributesInfo)
{
// Get the single syntax reference for the input method symbol (there should be only one)
if (methodSymbol.DeclaringSyntaxReferences is not [SyntaxReference syntaxReference])
@@ -1009,7 +1005,7 @@ static void GatherForwardedAttributes(
foreach (AttributeListSyntax attributeList in methodDeclaration.AttributeLists)
{
// Same as in the [ObservableProperty] generator, except we're also looking for fields here
- if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword or SyntaxKind.FieldKeyword))
+ if (attributeList.Target?.Identifier is not SyntaxToken(SyntaxKind.PropertyKeyword or SyntaxKind.FieldKeyword) targetIdentifier)
{
continue;
}
@@ -1033,7 +1029,7 @@ static void GatherForwardedAttributes(
IEnumerable attributeArguments = attribute.ArgumentList?.Arguments ?? Enumerable.Empty();
// Try to extract the forwarded attribute
- if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, token, out AttributeInfo? attributeInfo))
+ if (!AttributeInfo.TryCreate(attributeTypeSymbol, semanticModel, attributeArguments, targetIdentifier.Kind(), token, out AttributeInfo? attributeInfo))
{
diagnostics.Add(
InvalidFieldOrPropertyTargetedAttributeExpressionOnRelayCommandMethod,
@@ -1044,15 +1040,8 @@ static void GatherForwardedAttributes(
continue;
}
- // Add the new attribute info to the right builder
- if (attributeList.Target?.Identifier is SyntaxToken(SyntaxKind.FieldKeyword))
- {
- fieldAttributesInfo.Add(attributeInfo);
- }
- else
- {
- propertyAttributesInfo.Add(attributeInfo);
- }
+ // Add the new attribute info to the builder
+ forwardedAttributesInfo.Add(attributeInfo);
}
}
}
@@ -1064,17 +1053,16 @@ static void GatherForwardedAttributes(
IMethodSymbol partialImplementation = methodSymbol.PartialImplementationPart ?? methodSymbol;
// We always give priority to the partial definition, to ensure a predictable and testable ordering
- GatherForwardedAttributes(partialDefinition, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
- GatherForwardedAttributes(partialImplementation, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
+ GatherForwardedAttributes(partialDefinition, semanticModel, token, in diagnostics, in forwardedAttributesInfo);
+ GatherForwardedAttributes(partialImplementation, semanticModel, token, in diagnostics, in forwardedAttributesInfo);
}
else
{
// If the method is not a partial definition/implementation, just gather attributes from the method with no modifications
- GatherForwardedAttributes(methodSymbol, semanticModel, token, in diagnostics, in fieldAttributesInfo, in propertyAttributesInfo);
+ GatherForwardedAttributes(methodSymbol, semanticModel, token, in diagnostics, in forwardedAttributesInfo);
}
- fieldAttributes = fieldAttributesInfo.ToImmutable();
- propertyAttributes = propertyAttributesInfo.ToImmutable();
+ forwardedAttributes = forwardedAttributesInfo.ToImmutable();
}
}
}
diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
index a1e954d85..6997cfaa1 100644
--- a/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
+++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.UnitTests/Test_SourceGeneratorsCodegen.cs
@@ -3296,6 +3296,104 @@ public T Value
VerifyGenerateSources(source, new[] { new ObservablePropertyGenerator() }, ("MyApp.MyViewModel`1.g.cs", result));
}
+ [TestMethod]
+ public void ObservablePropertyWithForwardedAttributes_OnPropertyAccessors()
+ {
+ string source = """
+ using System.ComponentModel;
+ using CommunityToolkit.Mvvm.ComponentModel;
+
+ #nullable enable
+
+ namespace MyApp;
+
+ partial class MyViewModel : ObservableObject
+ {
+ [ObservableProperty]
+ [property: Test("Property1")]
+ [property: Test("Property2")]
+ [property: Test("Property3")]
+ [get: Test("Get1")]
+ [get: Test("Get2")]
+ [set: Test("Set1")]
+ [set: Test("Set2")]
+ private object? a;
+ }
+
+ public class TestAttribute : Attribute
+ {
+ public TestAttribute(string value)
+ {
+ }
+ }
+ """;
+
+ string result = """
+ //
+ #pragma warning disable
+ #nullable enable
+ namespace MyApp
+ {
+ ///
+ partial class MyViewModel
+ {
+ ///
+ [global::System.CodeDom.Compiler.GeneratedCode("CommunityToolkit.Mvvm.SourceGenerators.ObservablePropertyGenerator", )]
+ [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
+ [global::MyApp.TestAttribute("Property1")]
+ [global::MyApp.TestAttribute("Property2")]
+ [global::MyApp.TestAttribute("Property3")]
+ public object? A
+ {
+ [global::MyApp.TestAttribute("Get1")]
+ [global::MyApp.TestAttribute("Get2")]
+ get => a;
+ [global::MyApp.TestAttribute("Set1")]
+ [global::MyApp.TestAttribute("Set2")]
+ set
+ {
+ if (!global::System.Collections.Generic.EqualityComparer