// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics.Contracts; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics; using CommunityToolkit.Mvvm.SourceGenerators.Extensions; using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; using static Microsoft.CodeAnalysis.SymbolDisplayTypeQualificationStyle; using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors; namespace CommunityToolkit.Mvvm.SourceGenerators; /// /// A source generator for the ObservablePropertyAttribute type. /// [Generator] public sealed partial class ObservablePropertyGenerator : ISourceGenerator { /// public void Initialize(GeneratorInitializationContext context) { context.RegisterForSyntaxNotifications(static () => new SyntaxReceiver()); } /// public void Execute(GeneratorExecutionContext context) { // Get the syntax receiver with the candidate nodes if (context.SyntaxContextReceiver is not SyntaxReceiver syntaxReceiver || syntaxReceiver.GatheredInfo.Count == 0) { return; } // Validate the language version. Note that we're emitting this diagnostic in each generator (excluding the one // only emitting the nullability annotation attributes if missing) so that the diagnostic is emitted only when // users are using one of these generators, and not by default as soon as they add a reference to the MVVM Toolkit. // This ensures that users not using any of the source generators won't be broken when upgrading to this new version. if (context.ParseOptions is not CSharpParseOptions { LanguageVersion: >= LanguageVersion.CSharp9 }) { context.ReportDiagnostic(Diagnostic.Create(UnsupportedCSharpLanguageVersionError, null)); } // Sets of discovered property names HashSet propertyChangedNames = new(); HashSet propertyChangingNames = new(); // Process the annotated fields foreach (IGrouping? items in syntaxReceiver.GatheredInfo.GroupBy(static item => item.FieldSymbol.ContainingType, SymbolEqualityComparer.Default)) { if (items.Key.DeclaringSyntaxReferences.Length > 0 && items.Key.DeclaringSyntaxReferences.First().GetSyntax() is ClassDeclarationSyntax classDeclaration) { try { OnExecuteForProperties(context, classDeclaration, items.Key, items, propertyChangedNames, propertyChangingNames); } catch { context.ReportDiagnostic(ObservablePropertyGeneratorError, items.Key, items.Key); } } } // Process the fields for the cached args OnExecuteForPropertyArgs(context, propertyChangedNames, propertyChangingNames); } /// /// Processes a given target type for declared observable properties. /// /// The input instance to use. /// The node to process. /// The for . /// The sequence of fields to process. /// The collection of discovered property changed names. /// The collection of discovered property changing names. private static void OnExecuteForProperties( GeneratorExecutionContext context, ClassDeclarationSyntax classDeclaration, INamedTypeSymbol classDeclarationSymbol, IEnumerable items, ICollection propertyChangedNames, ICollection propertyChangingNames) { INamedTypeSymbol iNotifyPropertyChangingSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanging")!; INamedTypeSymbol observableObjectSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObject")!; INamedTypeSymbol observableObjectAttributeSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute")!; INamedTypeSymbol observableValidatorSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")!; // Check whether the current type implements INotifyPropertyChanging and whether it inherits from ObservableValidator bool isObservableObject = classDeclarationSymbol.InheritsFrom(observableObjectSymbol); bool isObservableValidator = classDeclarationSymbol.InheritsFrom(observableValidatorSymbol); bool isNotifyPropertyChanging = isObservableObject || classDeclarationSymbol.AllInterfaces.Contains(iNotifyPropertyChangingSymbol, SymbolEqualityComparer.Default) || classDeclarationSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, observableObjectAttributeSymbol)); // Create the class declaration for the user type. This will produce a tree as follows: // // // { // // } ClassDeclarationSyntax? classDeclarationSyntax = ClassDeclaration(classDeclarationSymbol.Name) .WithModifiers(classDeclaration.Modifiers) .AddMembers(items.Select(item => CreatePropertyDeclaration( context, item.LeadingTrivia, item.FieldSymbol, isNotifyPropertyChanging, isObservableValidator, propertyChangedNames, propertyChangingNames)).ToArray()); TypeDeclarationSyntax typeDeclarationSyntax = classDeclarationSyntax; // Add all parent types in ascending order, if any foreach (TypeDeclarationSyntax? parentType in classDeclaration.Ancestors().OfType()) { typeDeclarationSyntax = parentType .WithMembers(SingletonList(typeDeclarationSyntax)) .WithConstraintClauses(List()) .WithBaseList(null) .WithAttributeLists(List()) .WithoutTrivia(); } // Create the compilation unit with the namespace and target member. // From this, we can finally generate the source code to output. string? namespaceName = classDeclarationSymbol.ContainingNamespace.ToDisplayString(new(typeQualificationStyle: NameAndContainingTypesAndNamespaces)); // Create the final compilation unit to generate (with leading trivia) string? source = CompilationUnit().AddMembers( NamespaceDeclaration(IdentifierName(namespaceName)).WithLeadingTrivia(TriviaList( Comment("// "), Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)))) .AddMembers(typeDeclarationSyntax)) .NormalizeWhitespace() .ToFullString(); // Add the partial type context.AddSource($"{classDeclarationSymbol.GetFullMetadataNameForFileName()}.cs", SourceText.From(source, Encoding.UTF8)); } /// /// Creates a instance for a specified field. /// /// The input instance to use. /// The leading trivia for the field to process. /// The input instance to process. /// Indicates whether or not is also implemented. /// Indicates whether or not the containing type inherits from ObservableValidator. /// The collection of discovered property changed names. /// The collection of discovered property changing names. /// A generated instance for the input field. [Pure] private static PropertyDeclarationSyntax CreatePropertyDeclaration( GeneratorExecutionContext context, SyntaxTriviaList leadingTrivia, IFieldSymbol fieldSymbol, bool isNotifyPropertyChanging, bool isObservableValidator, ICollection propertyChangedNames, ICollection propertyChangingNames) { // Get the field type and the target property name string typeName = fieldSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); string propertyName = GetGeneratedPropertyName(fieldSymbol); INamedTypeSymbol alsoNotifyChangeForAttributeSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyChangeForAttribute")!; INamedTypeSymbol alsoNotifyCanExecuteForAttributeSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.AlsoNotifyCanExecuteForAttribute")!; INamedTypeSymbol? validationAttributeSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.DataAnnotations.ValidationAttribute"); List dependentNotificationStatements = new(); List validationAttributes = new(); foreach (AttributeData attributeData in fieldSymbol.GetAttributes()) { // Add dependent property notifications, if needed if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, alsoNotifyChangeForAttributeSymbol)) { foreach (string dependentPropertyName in attributeData.GetConstructorArguments()) { propertyChangedNames.Add(dependentPropertyName); // OnPropertyChanged(""); dependentNotificationStatements.Add(ExpressionStatement( InvocationExpression(IdentifierName("OnPropertyChanged")) .AddArgumentListArguments(Argument(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs"), IdentifierName($"{dependentPropertyName}{nameof(PropertyChangedEventArgs)}")))))); } } else if (SymbolEqualityComparer.Default.Equals(attributeData.AttributeClass, alsoNotifyCanExecuteForAttributeSymbol)) { // Add dependent relay command notifications, if needed foreach (string commandName in attributeData.GetConstructorArguments()) { // .NotifyCanExecuteChanged(); dependentNotificationStatements.Add(ExpressionStatement( InvocationExpression(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName(commandName), IdentifierName("NotifyCanExecuteChanged"))))); } } else if (validationAttributeSymbol is not null && attributeData.AttributeClass?.InheritsFrom(validationAttributeSymbol) == true) { // Track the current validation attribute validationAttributes.Add(attributeData.AsAttributeSyntax()); } } // In case the backing field is exactly named "value", we need to add the "this." prefix to ensure that comparisons and assignments // with it in the generated setter body are executed correctly and without conflicts with the implicit value parameter. ExpressionSyntax fieldExpression = fieldSymbol.Name switch { "value" => MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, ThisExpression(), IdentifierName("value")), string name => IdentifierName(name) }; BlockSyntax setterBlock; if (validationAttributes.Count > 0) { // Emit a diagnostic if the current type doesn't inherit from ObservableValidator if (!isObservableValidator) { context.ReportDiagnostic( MissingObservableValidatorInheritanceError, fieldSymbol, fieldSymbol.ContainingType, fieldSymbol.Name, validationAttributes.Count); setterBlock = Block(); } else { propertyChangedNames.Add(propertyName); propertyChangingNames.Add(propertyName); // Generate the inner setter block as follows: // // if (!global::System.Collections.Generic.EqualityComparer<>.Default.Equals(this., value)) // { // OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangingEventArgs); // Optional // this. = value; // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // ValidateProperty(value, ); // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // Optional // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // ... // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // .NotifyCanExecuteChanged(); // Optional // .NotifyCanExecuteChanged(); // ... // .NotifyCanExecuteChanged(); // } // // The reason why the code is explicitly generated instead of just calling ObservableValidator.SetProperty() is so that we can // take advantage of the cached property changed arguments for the current property as well, not just for the dependent ones. setterBlock = Block( IfStatement( PrefixUnaryExpression( SyntaxKind.LogicalNotExpression, InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, GenericName(Identifier("global::System.Collections.Generic.EqualityComparer")) .AddTypeArgumentListArguments(IdentifierName(typeName)), IdentifierName("Default")), IdentifierName("Equals"))) .AddArgumentListArguments( Argument(fieldExpression), Argument(IdentifierName("value")))), Block( ExpressionStatement( InvocationExpression(IdentifierName("OnPropertyChanging")) .AddArgumentListArguments(Argument(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs"), IdentifierName($"{propertyName}{nameof(PropertyChangingEventArgs)}"))))), ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, fieldExpression, IdentifierName("value"))), ExpressionStatement( InvocationExpression(IdentifierName("OnPropertyChanged")) .AddArgumentListArguments(Argument(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs"), IdentifierName($"{propertyName}{nameof(PropertyChangedEventArgs)}"))))), ExpressionStatement( InvocationExpression(IdentifierName("ValidateProperty")) .AddArgumentListArguments( Argument(IdentifierName("value")), Argument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyName)))))) .AddStatements(dependentNotificationStatements.ToArray()))); } } else { BlockSyntax updateAndNotificationBlock = Block(); // Add OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyNamePropertyChangingEventArgs) if necessary if (isNotifyPropertyChanging) { propertyChangingNames.Add(propertyName); updateAndNotificationBlock = updateAndNotificationBlock.AddStatements(ExpressionStatement( InvocationExpression(IdentifierName("OnPropertyChanging")) .AddArgumentListArguments(Argument(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs"), IdentifierName($"{propertyName}{nameof(PropertyChangingEventArgs)}")))))); } propertyChangedNames.Add(propertyName); // Add the following statements: // // = value; // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyNamePropertyChangedEventArgs); updateAndNotificationBlock = updateAndNotificationBlock.AddStatements( ExpressionStatement( AssignmentExpression( SyntaxKind.SimpleAssignmentExpression, fieldExpression, IdentifierName("value"))), ExpressionStatement( InvocationExpression(IdentifierName("OnPropertyChanged")) .AddArgumentListArguments(Argument(MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, IdentifierName("global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs"), IdentifierName($"{propertyName}{nameof(PropertyChangedEventArgs)}")))))); // Add the dependent property notifications at the end updateAndNotificationBlock = updateAndNotificationBlock.AddStatements(dependentNotificationStatements.ToArray()); // Generate the inner setter block as follows: // // if (!global::System.Collections.Generic.EqualityComparer<>.Default.Equals(, value)) // { // OnPropertyChanging(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangingEventArgs); // Optional // = value; // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // Optional // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // ... // OnPropertyChanged(global::CommunityToolkit.Mvvm.ComponentModel.__Internals.__KnownINotifyPropertyChangedOrChangingArgs.PropertyChangedEventArgs); // .NotifyCanExecuteChanged(); // Optional // .NotifyCanExecuteChanged(); // ... // .NotifyCanExecuteChanged(); // } setterBlock = Block( IfStatement( PrefixUnaryExpression( SyntaxKind.LogicalNotExpression, InvocationExpression( MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, MemberAccessExpression( SyntaxKind.SimpleMemberAccessExpression, GenericName(Identifier("global::System.Collections.Generic.EqualityComparer")) .AddTypeArgumentListArguments(IdentifierName(typeName)), IdentifierName("Default")), IdentifierName("Equals"))) .AddArgumentListArguments( Argument(fieldExpression), Argument(IdentifierName("value")))), updateAndNotificationBlock)); } // Get the right type for the declared property (including nullability annotations) TypeSyntax propertyType = IdentifierName(typeName); if (fieldSymbol.Type is { IsReferenceType: true, NullableAnnotation: NullableAnnotation.Annotated }) { propertyType = NullableType(propertyType); } // Construct the generated property as follows: // // // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] // [global::System.Diagnostics.DebuggerNonUserCode] // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // // Optional // // ... // // public // { // get => ; // set // { // // } // } return PropertyDeclaration(propertyType, Identifier(propertyName)) .AddAttributeLists( AttributeList(SingletonSeparatedList( Attribute(IdentifierName("global::System.CodeDom.Compiler.GeneratedCode")) .AddArgumentListArguments( AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservablePropertyGenerator).FullName))), AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(typeof(ObservablePropertyGenerator).Assembly.GetName().Version.ToString())))))), AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))), AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage"))))) .AddAttributeLists(validationAttributes.Select(static a => AttributeList(SingletonSeparatedList(a))).ToArray()) .WithLeadingTrivia(leadingTrivia.Where(static trivia => !trivia.IsKind(SyntaxKind.RegionDirectiveTrivia) && !trivia.IsKind(SyntaxKind.EndRegionDirectiveTrivia))) .AddModifiers(Token(SyntaxKind.PublicKeyword)) .AddAccessorListAccessors( AccessorDeclaration(SyntaxKind.GetAccessorDeclaration) .WithExpressionBody(ArrowExpressionClause(IdentifierName(fieldSymbol.Name))) .WithSemicolonToken(Token(SyntaxKind.SemicolonToken)), AccessorDeclaration(SyntaxKind.SetAccessorDeclaration) .WithBody(setterBlock)); } /// /// Get the generated property name for an input field. /// /// The input instance to process. /// The generated property name for . [Pure] public static string GetGeneratedPropertyName(IFieldSymbol fieldSymbol) { string propertyName = fieldSymbol.Name; if (propertyName.StartsWith("m_")) { propertyName = propertyName.Substring(2); } else if (propertyName.StartsWith("_")) { propertyName = propertyName.TrimStart('_'); } return $"{char.ToUpper(propertyName[0])}{propertyName.Substring(1)}"; } /// /// Processes the cached property changed/changing args. /// /// The input instance to use. /// The collection of discovered property changed names. /// The collection of discovered property changing names. public void OnExecuteForPropertyArgs(GeneratorExecutionContext context, IReadOnlyCollection propertyChangedNames, IReadOnlyCollection propertyChangingNames) { if (propertyChangedNames.Count == 0 && propertyChangingNames.Count == 0) { return; } INamedTypeSymbol propertyChangedEventArgsSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.PropertyChangedEventArgs")!; INamedTypeSymbol propertyChangingEventArgsSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.PropertyChangingEventArgs")!; // Create a static method to validate all properties in a given class. // This code takes a class symbol and produces a compilation unit as follows: // // // // // #pragma warning disable // // namespace CommunityToolkit.Mvvm.ComponentModel.__Internals // { // [global::System.CodeDom.Compiler.GeneratedCode("...", "...")] // [global::System.Diagnostics.DebuggerNonUserCode] // [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] // [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] // [global::System.Obsolete("This type is not intended to be used directly by user code")] // internal static class __KnownINotifyPropertyChangedOrChangingArgs // { // // } // } string? source = CompilationUnit().AddMembers( NamespaceDeclaration(IdentifierName("CommunityToolkit.Mvvm.ComponentModel.__Internals")).WithLeadingTrivia(TriviaList( Comment("// "), Trivia(PragmaWarningDirectiveTrivia(Token(SyntaxKind.DisableKeyword), true)))).AddMembers( ClassDeclaration("__KnownINotifyPropertyChangedOrChangingArgs").AddModifiers( Token(SyntaxKind.InternalKeyword), Token(SyntaxKind.StaticKeyword)).AddAttributeLists( AttributeList(SingletonSeparatedList( Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode")) .AddArgumentListArguments( AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(GetType().FullName))), AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(GetType().Assembly.GetName().Version.ToString())))))), AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))), AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))), AttributeList(SingletonSeparatedList( Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments( AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))), AttributeList(SingletonSeparatedList( Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments( AttributeArgument(LiteralExpression( SyntaxKind.StringLiteralExpression, Literal("This type is not intended to be used directly by user code"))))))) .AddMembers(propertyChangedNames.Select(name => CreateFieldDeclaration(propertyChangedEventArgsSymbol, name)).ToArray()) .AddMembers(propertyChangingNames.Select(name => CreateFieldDeclaration(propertyChangingEventArgsSymbol, name)).ToArray()))) .NormalizeWhitespace() .ToFullString(); // Add the partial type context.AddSource("__KnownINotifyPropertyChangedOrChangingArgs.cs", SourceText.From(source, Encoding.UTF8)); } /// /// Creates a field declaration for a cached property change name. /// /// The type of cached property change argument (either or ). /// The name of the cached property name. /// A instance for the input cached property name. [Pure] private static FieldDeclarationSyntax CreateFieldDeclaration(INamedTypeSymbol type, string propertyName) { // Create a static field with a cached property changed/changing argument for a specified property. // This code produces a field declaration as follows: // // [global::System.ComponentModel.EditorBrowsable(global::System.ComponentModel.EditorBrowsableState.Never)] // [global::System.Obsolete("This field is not intended to be referenced directly by user code")] // public static readonly = new(""); return FieldDeclaration( VariableDeclaration(IdentifierName(type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))) .AddVariables( VariableDeclarator(Identifier($"{propertyName}{type.Name}")) .WithInitializer(EqualsValueClause( ImplicitObjectCreationExpression() .AddArgumentListArguments(Argument( LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(propertyName)))))))) .AddModifiers( Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.ReadOnlyKeyword)) .AddAttributeLists( AttributeList(SingletonSeparatedList( Attribute(IdentifierName("global::System.ComponentModel.EditorBrowsable")).AddArgumentListArguments( AttributeArgument(ParseExpression("global::System.ComponentModel.EditorBrowsableState.Never"))))), AttributeList(SingletonSeparatedList( Attribute(IdentifierName("global::System.Obsolete")).AddArgumentListArguments( AttributeArgument(LiteralExpression( SyntaxKind.StringLiteralExpression, Literal("This field is not intended to be referenced directly by user code"))))))); } }