diff --git a/CliFx.SourceGeneration/CommandSchemaGenerator.cs b/CliFx.SourceGeneration/CommandSchemaGenerator.cs index 7d9d5b3..2dc3768 100644 --- a/CliFx.SourceGeneration/CommandSchemaGenerator.cs +++ b/CliFx.SourceGeneration/CommandSchemaGenerator.cs @@ -1,5 +1,4 @@ -using System; -using System.Linq; +using System.Linq; using CliFx.SourceGeneration.SemanticModel; using CliFx.SourceGeneration.Utils.Extensions; using Microsoft.CodeAnalysis; @@ -21,13 +20,13 @@ public class CommandSchemaGenerator : IIncrementalGenerator (n, _) => n is TypeDeclarationSyntax, (x, _) => { - // Predicate ensures that these casts are safe - var typeDeclarationSyntax = (TypeDeclarationSyntax)x.TargetNode; - var namedTypeSymbol = (INamedTypeSymbol)x.TargetSymbol; + // Predicate above ensures that these casts are safe + var commandTypeSyntax = (TypeDeclarationSyntax)x.TargetNode; + var commandTypeSymbol = (INamedTypeSymbol)x.TargetSymbol; // Check if the target type and all its containing types are partial if ( - typeDeclarationSyntax + commandTypeSyntax .AncestorsAndSelf() .Any(a => a is TypeDeclarationSyntax t @@ -39,14 +38,14 @@ public class CommandSchemaGenerator : IIncrementalGenerator null, Diagnostic.Create( DiagnosticDescriptors.CommandMustBePartial, - typeDeclarationSyntax.Identifier.GetLocation() + commandTypeSyntax.Identifier.GetLocation() ) ); } // Check if the target type implements ICommand - var hasCommandInterface = namedTypeSymbol.AllInterfaces.Any(i => - i.DisplayNameMatches("CliFx.ICommand") + var hasCommandInterface = commandTypeSymbol.AllInterfaces.Any(i => + i.DisplayNameMatches(KnownSymbolNames.CliFxCommandInterface) ); if (!hasCommandInterface) @@ -55,220 +54,22 @@ public class CommandSchemaGenerator : IIncrementalGenerator null, Diagnostic.Create( DiagnosticDescriptors.CommandMustImplementInterface, - namedTypeSymbol.Locations.First() + commandTypeSymbol.Locations.First() ) ); } - // Get the command name + // Resolve the command var commandAttribute = x.Attributes.First(a => a.AttributeClass?.DisplayNameMatches(KnownSymbolNames.CliFxCommandAttribute) == true ); - var commandName = - commandAttribute.ConstructorArguments.FirstOrDefault().Value as string; + var command = CommandSymbol.FromSymbol(commandTypeSymbol, commandAttribute); - var commandDescription = - commandAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Description", StringComparison.Ordinal) - ) - .Value.Value as string; + // TODO: validate command - // Get all parameter inputs - var parameterSymbols = namedTypeSymbol - .GetMembers() - .OfType() - .Select(p => - { - var parameterAttribute = p.GetAttributes() - .FirstOrDefault(a => - a.AttributeClass?.DisplayNameMatches( - KnownSymbolNames.CliFxCommandParameterAttribute - ) == true - ); - - if (parameterAttribute is null) - return null; - - var isSequence = false; // TODO - - var order = parameterAttribute.ConstructorArguments.First().Value as int?; - - var isRequired = - parameterAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "IsRequired", StringComparison.Ordinal) - ) - .Value.Value as bool? - ?? true; - - var name = - parameterAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Name", StringComparison.Ordinal) - ) - .Value.Value as string; - - var description = - parameterAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Description", StringComparison.Ordinal) - ) - .Value.Value as string; - - var converter = - parameterAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Converter", StringComparison.Ordinal) - ) - .Value.Value as ITypeSymbol; - - var validators = parameterAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Validators", StringComparison.Ordinal) - ) - .Value.Values.CastArray(); - - return new CommandParameterSymbol( - new PropertyDescriptor( - new TypeDescriptor( - p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - ), - p.Name - ), - isSequence, - order, - isRequired, - name, - description, - converter - ?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - ?.Pipe(n => new TypeDescriptor(n)), - validators - .Select(v => - v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - ) - .Select(n => new TypeDescriptor(n)) - .ToArray() - ); - }) - .WhereNotNull() - .ToArray(); - - // Get all option inputs - var optionSymbols = namedTypeSymbol - .GetMembers() - .OfType() - .Select(p => - { - var optionAttribute = p.GetAttributes() - .FirstOrDefault(a => - a.AttributeClass?.DisplayNameMatches( - KnownSymbolNames.CliFxCommandOptionAttribute - ) == true - ); - - if (optionAttribute is null) - return null; - - var isSequence = false; // TODO - - var name = - optionAttribute - .ConstructorArguments.Where(a => - a.Type?.SpecialType == SpecialType.System_String - ) - .Select(a => a.Value) - .FirstOrDefault() as string; - - var shortName = - optionAttribute - .ConstructorArguments.Where(a => - a.Type?.SpecialType == SpecialType.System_Char - ) - .Select(a => a.Value) - .FirstOrDefault() as char?; - - var environmentVariable = - optionAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals( - a.Key, - "EnvironmentVariable", - StringComparison.Ordinal - ) - ) - .Value.Value as string; - - var isRequired = - optionAttribute - .NamedArguments.Where(a => a.Key == "IsRequired") - .Select(a => a.Value.Value) - .FirstOrDefault() as bool? - ?? p.IsRequired; - - var description = - optionAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Description", StringComparison.Ordinal) - ) - .Value.Value as string; - - var converter = - optionAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Converter", StringComparison.Ordinal) - ) - .Value.Value as ITypeSymbol; - - var validators = optionAttribute - .NamedArguments.FirstOrDefault(a => - string.Equals(a.Key, "Validators", StringComparison.Ordinal) - ) - .Value.Values.CastArray(); - - return new CommandOptionSymbol( - new PropertyDescriptor( - new TypeDescriptor( - p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - ), - p.Name - ), - isSequence, - name, - shortName, - environmentVariable, - isRequired, - description, - converter - ?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - ?.Pipe(n => new TypeDescriptor(n)), - validators - .Select(v => - v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat) - ) - .Select(n => new TypeDescriptor(n)) - .ToArray() - ); - }) - .WhereNotNull() - .ToArray(); - - return ( - new CommandSymbol( - new TypeDescriptor( - namedTypeSymbol.ToDisplayString( - SymbolDisplayFormat.FullyQualifiedFormat - ) - ), - commandName, - commandDescription, - parameterSymbols.Concat(optionSymbols).ToArray() - ), - null - ); + return (command, null); } ); @@ -276,58 +77,54 @@ public class CommandSchemaGenerator : IIncrementalGenerator var diagnostics = values.Select((v, _) => v.Item2).WhereNotNull(); context.RegisterSourceOutput(diagnostics, (x, d) => x.ReportDiagnostic(d)); - // Generate source + // Generate command schemas var symbols = values.Select((v, _) => v.Item1).WhereNotNull(); context.RegisterSourceOutput( symbols, (x, c) => - { - var source = + x.AddSource( + $"{c.Type.FullyQualifiedName}.CommandSchema.Generated.cs", // lang=csharp $$""" - using System.Linq; - using CliFx.Schema; - using CliFx.Extensibility; - - namespace {{ c.Type.Namespace }}; - - partial class {{ c.Type.Name }} - { - public static CommandSchema<{{ c.Type.FullyQualifiedName }}> Schema { get; } = new( - {{ c.Name }}, - {{ c.Description }}, - [ - {{ c.Inputs.Select(i => i switch { - CommandParameterSymbol parameter => - // lang=csharp - $$""" - new CommandParameterSchema<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>( - new PropertyBinding<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>( - obj => obj.{{ i.Property.Name }}, - (obj, value) => obj.{{ i.Property.Name }} = value - ), - p.Order, - p.IsRequired, - p.Name, - p.Description, - new {{ i.ConverterType.FullyQualifiedName }}(), - [ - {{ i.ValidatorTypes.Select(v => - // lang=csharp - $"new {v.FullyQualifiedName}()").JoinToString(",\n") - }} - ] - ) - """, - CommandOptionSymbol option => "" - }).JoinToString(",\n") - }} - ] - } - """; + namespace {{c.Type.Namespace}}; - x.AddSource($"{c.TypeName}.CommandSchema.Generated.cs", source); - } + partial class {{c.Type.Name}} + { + public static CliFx.Schema.CommandSchema<{{c.Type.FullyQualifiedName}}> Schema { get; } = {{c.GenerateSchemaInitializationCode()}}; + } + """ + ) + ); + + // Generate extension methods + var symbolsCollected = symbols.Collect(); + context.RegisterSourceOutput( + symbolsCollected, + (x, cs) => + x.AddSource( + "CommandSchemaExtensions.Generated.cs", + // lang=csharp + $$""" + namespace CliFx; + + static partial class GeneratedExtensions + { + public static CliFx.CliApplicationBuilder AddCommandsFromThisAssembly(this CliFx.CliApplicationBuilder builder) + { + {{ + cs.Select(c => c.Type.FullyQualifiedName) + .Select(t => + // lang=csharp + $"builder.AddCommand({t}.Schema);" + ) + .JoinToString("\n") + }} + + return builder; + } + } + """ + ) ); } } diff --git a/CliFx.SourceGeneration/SemanticModel/CommandInputSymbol.cs b/CliFx.SourceGeneration/SemanticModel/CommandInputSymbol.cs index 3ea3c59..3af815d 100644 --- a/CliFx.SourceGeneration/SemanticModel/CommandInputSymbol.cs +++ b/CliFx.SourceGeneration/SemanticModel/CommandInputSymbol.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.SemanticModel; @@ -54,3 +55,12 @@ internal partial class CommandInputSymbol : IEquatable public override int GetHashCode() => HashCode.Combine(Property, IsSequence, Description, ConverterType, ValidatorTypes); } + +internal partial class CommandInputSymbol +{ + public static bool IsSequenceType(ITypeSymbol type) => + type.AllInterfaces.Any(i => + i.SpecialType == SpecialType.System_Collections_Generic_IEnumerable_T + ) + && type.SpecialType != SpecialType.System_String; +} diff --git a/CliFx.SourceGeneration/SemanticModel/CommandOptionSymbol.cs b/CliFx.SourceGeneration/SemanticModel/CommandOptionSymbol.cs index 5bf038f..08e9be9 100644 --- a/CliFx.SourceGeneration/SemanticModel/CommandOptionSymbol.cs +++ b/CliFx.SourceGeneration/SemanticModel/CommandOptionSymbol.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using CliFx.SourceGeneration.Utils.Extensions; +using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.SemanticModel; @@ -55,3 +58,33 @@ internal partial class CommandOptionSymbol : IEquatable public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Name, ShortName, EnvironmentVariable, IsRequired); } + +internal partial class CommandOptionSymbol +{ + public static CommandOptionSymbol FromSymbol( + IPropertySymbol property, + AttributeData attribute + ) => + new( + PropertyDescriptor.FromSymbol(property), + IsSequenceType(property.Type), + attribute + .ConstructorArguments.FirstOrDefault(a => + a.Type?.SpecialType == SpecialType.System_String + ) + .Value as string, + attribute + .ConstructorArguments.FirstOrDefault(a => + a.Type?.SpecialType == SpecialType.System_Char + ) + .Value as char?, + attribute.GetNamedArgumentValue("EnvironmentVariable", default(string)), + attribute.GetNamedArgumentValue("IsRequired", property.IsRequired), + attribute.GetNamedArgumentValue("Description", default(string)), + TypeDescriptor.FromSymbol(attribute.GetNamedArgumentValue("Converter")), + attribute + .GetNamedArgumentValues("Validators") + .Select(TypeDescriptor.FromSymbol) + .ToArray() + ); +} diff --git a/CliFx.SourceGeneration/SemanticModel/CommandParameterSymbol.cs b/CliFx.SourceGeneration/SemanticModel/CommandParameterSymbol.cs index 81471a6..41b30e1 100644 --- a/CliFx.SourceGeneration/SemanticModel/CommandParameterSymbol.cs +++ b/CliFx.SourceGeneration/SemanticModel/CommandParameterSymbol.cs @@ -1,5 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using CliFx.SourceGeneration.Utils.Extensions; +using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.SemanticModel; @@ -51,3 +54,24 @@ internal partial class CommandParameterSymbol : IEquatable HashCode.Combine(base.GetHashCode(), Order, Name, IsRequired); } + +internal partial class CommandParameterSymbol +{ + public static CommandParameterSymbol FromSymbol( + IPropertySymbol property, + AttributeData attribute + ) => + new( + PropertyDescriptor.FromSymbol(property), + IsSequenceType(property.Type), + (int)attribute.ConstructorArguments.First().Value!, + attribute.GetNamedArgumentValue("Name", default(string)), + attribute.GetNamedArgumentValue("IsRequired", true), + attribute.GetNamedArgumentValue("Description", default(string)), + TypeDescriptor.FromSymbol(attribute.GetNamedArgumentValue("Converter")), + attribute + .GetNamedArgumentValues("Validators") + .Select(TypeDescriptor.FromSymbol) + .ToArray() + ); +} diff --git a/CliFx.SourceGeneration/SemanticModel/CommandSymbol.cs b/CliFx.SourceGeneration/SemanticModel/CommandSymbol.cs index aa174f2..61226f6 100644 --- a/CliFx.SourceGeneration/SemanticModel/CommandSymbol.cs +++ b/CliFx.SourceGeneration/SemanticModel/CommandSymbol.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using CliFx.SourceGeneration.Utils.Extensions; +using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.SemanticModel; @@ -24,6 +26,73 @@ internal partial class CommandSymbol( public IReadOnlyList Options => Inputs.OfType().ToArray(); + + private string GeneratePropertyBindingInitializationCode(PropertyDescriptor property) => + // lang=csharp + $$""" + new CliFx.Schema.PropertyBinding<{{Type.FullyQualifiedName}}, {{property + .Type + .FullyQualifiedName}}>( + (obj) => obj.{{property.Name}}, + (obj, value) => obj.{{property.Name}} = value + ) + """; + + private string GenerateSchemaInitializationCode(CommandInputSymbol input) => + input switch + { + CommandParameterSymbol parameter + => + // lang=csharp + $$""" + new CliFx.Schema.CommandParameterSchema<{{Type.FullyQualifiedName}}, {{parameter + .Property + .Type + .FullyQualifiedName}}>( + {{GeneratePropertyBindingInitializationCode(parameter.Property)}}, + {{parameter.IsSequence}}, + {{parameter.Order}}, + "{{parameter.Name}}", + {{parameter.IsRequired}}, + "{{parameter.Description}}", + // TODO, + // TODO + ); + """, + CommandOptionSymbol option + => + // lang=csharp + $$""" + new CliFx.Schema.CommandOptionSchema<{{Type.FullyQualifiedName}}, {{option + .Property + .Type + .FullyQualifiedName}}>( + {{GeneratePropertyBindingInitializationCode(option.Property)}}, + {{option.IsSequence}}, + "{{option.Name}}", + '{{option.ShortName}}', + "{{option.EnvironmentVariable}}", + {{option.IsRequired}}, + "{{option.Description}}", + // TODO, + // TODO + ); + """, + _ => throw new ArgumentOutOfRangeException(nameof(input), input, null) + }; + + public string GenerateSchemaInitializationCode() => + // lang=csharp + $$""" + new CliFx.Schema.CommandSchema<{{Type.FullyQualifiedName}}>( + "{{Name}}", + "{{Description}}", + new CliFx.Schema.CommandInputSchema[] + { + {{Inputs.Select(GenerateSchemaInitializationCode).JoinToString(",\n")}} + } + ) + """; } internal partial class CommandSymbol : IEquatable @@ -55,3 +124,44 @@ internal partial class CommandSymbol : IEquatable public override int GetHashCode() => HashCode.Combine(Type, Name, Description, Inputs); } + +internal partial class CommandSymbol +{ + public static CommandSymbol FromSymbol(INamedTypeSymbol symbol, AttributeData attribute) + { + var inputs = new List(); + foreach (var property in symbol.GetMembers().OfType()) + { + var parameterAttribute = property + .GetAttributes() + .FirstOrDefault(a => + a.AttributeClass?.Name == KnownSymbolNames.CliFxCommandParameterAttribute + ); + + if (parameterAttribute is not null) + { + inputs.Add(CommandParameterSymbol.FromSymbol(property, parameterAttribute)); + continue; + } + + var optionAttribute = property + .GetAttributes() + .FirstOrDefault(a => + a.AttributeClass?.Name == KnownSymbolNames.CliFxCommandOptionAttribute + ); + + if (optionAttribute is not null) + { + inputs.Add(CommandOptionSymbol.FromSymbol(property, optionAttribute)); + continue; + } + } + + return new CommandSymbol( + TypeDescriptor.FromSymbol(symbol), + attribute.ConstructorArguments.FirstOrDefault().Value as string, + attribute.GetNamedArgumentValue("Description", default(string)), + inputs + ); + } +} diff --git a/CliFx.SourceGeneration/SemanticModel/PropertyDescriptor.cs b/CliFx.SourceGeneration/SemanticModel/PropertyDescriptor.cs index 05278ed..bfd6795 100644 --- a/CliFx.SourceGeneration/SemanticModel/PropertyDescriptor.cs +++ b/CliFx.SourceGeneration/SemanticModel/PropertyDescriptor.cs @@ -1,4 +1,5 @@ using System; +using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.SemanticModel; @@ -35,3 +36,9 @@ internal partial class PropertyDescriptor : IEquatable public override int GetHashCode() => HashCode.Combine(Type, Name); } + +internal partial class PropertyDescriptor +{ + public static PropertyDescriptor FromSymbol(IPropertySymbol symbol) => + new(TypeDescriptor.FromSymbol(symbol.Type), symbol.Name); +} diff --git a/CliFx.SourceGeneration/SemanticModel/TypeDescriptor.cs b/CliFx.SourceGeneration/SemanticModel/TypeDescriptor.cs index 1fa61a5..bc5014b 100644 --- a/CliFx.SourceGeneration/SemanticModel/TypeDescriptor.cs +++ b/CliFx.SourceGeneration/SemanticModel/TypeDescriptor.cs @@ -1,5 +1,6 @@ using System; using CliFx.SourceGeneration.Utils.Extensions; +using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.SemanticModel; @@ -38,3 +39,9 @@ internal partial class TypeDescriptor : IEquatable public override int GetHashCode() => FullyQualifiedName.GetHashCode(); } + +internal partial class TypeDescriptor +{ + public static TypeDescriptor FromSymbol(ITypeSymbol symbol) => + new(symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); +} diff --git a/CliFx.SourceGeneration/Utils/Extensions/RoslynExtensions.cs b/CliFx.SourceGeneration/Utils/Extensions/RoslynExtensions.cs index 985dc03..36fe246 100644 --- a/CliFx.SourceGeneration/Utils/Extensions/RoslynExtensions.cs +++ b/CliFx.SourceGeneration/Utils/Extensions/RoslynExtensions.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; using Microsoft.CodeAnalysis; namespace CliFx.SourceGeneration.Utils.Extensions; @@ -13,6 +16,22 @@ internal static class RoslynExtensions StringComparison.Ordinal ); + public static T GetNamedArgumentValue( + this AttributeData attribute, + string name, + T defaultValue = default + ) => + attribute.NamedArguments.FirstOrDefault(i => i.Key == name).Value.Value is T valueAsT + ? valueAsT + : defaultValue; + + public static IReadOnlyList GetNamedArgumentValues( + this AttributeData attribute, + string name + ) + where T : class => + attribute.NamedArguments.FirstOrDefault(i => i.Key == name).Value.Values.CastArray(); + public static IncrementalValuesProvider WhereNotNull( this IncrementalValuesProvider values ) diff --git a/CliFx/Schema/CommandInputSchema.cs b/CliFx/Schema/CommandInputSchema.cs index eda2a71..fadd722 100644 --- a/CliFx/Schema/CommandInputSchema.cs +++ b/CliFx/Schema/CommandInputSchema.cs @@ -125,10 +125,7 @@ public abstract class CommandInputSchema( /// public abstract class CommandInputSchema< TCommand, - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicMethods - )] - TProperty + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty >( PropertyBinding property, bool isSequence, diff --git a/CliFx/Schema/CommandOptionSchema.cs b/CliFx/Schema/CommandOptionSchema.cs index 76c114c..4e3b2a5 100644 --- a/CliFx/Schema/CommandOptionSchema.cs +++ b/CliFx/Schema/CommandOptionSchema.cs @@ -63,10 +63,7 @@ public class CommandOptionSchema( /// public class CommandOptionSchema< TCommand, - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicMethods - )] - TProperty + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty >( PropertyBinding property, bool isSequence, diff --git a/CliFx/Schema/CommandParameterSchema.cs b/CliFx/Schema/CommandParameterSchema.cs index 3cd28eb..751238e 100644 --- a/CliFx/Schema/CommandParameterSchema.cs +++ b/CliFx/Schema/CommandParameterSchema.cs @@ -42,10 +42,7 @@ public class CommandParameterSchema( /// public class CommandParameterSchema< TCommand, - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicMethods - )] - TProperty + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty >( PropertyBinding property, bool isSequence, diff --git a/CliFx/Schema/PropertyBinding.cs b/CliFx/Schema/PropertyBinding.cs index def6f37..c692274 100644 --- a/CliFx/Schema/PropertyBinding.cs +++ b/CliFx/Schema/PropertyBinding.cs @@ -9,10 +9,7 @@ namespace CliFx.Schema; /// Provides read and write access to a CLR property. /// public class PropertyBinding( - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicMethods - )] - Type type, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type, Func getValue, Action setValue ) @@ -20,9 +17,7 @@ public class PropertyBinding( /// /// Underlying CLR type of the property. /// - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicMethods - )] + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] public Type Type { get; } = type; /// @@ -63,10 +58,7 @@ public class PropertyBinding( /// public class PropertyBinding< TObject, - [DynamicallyAccessedMembers( - DynamicallyAccessedMemberTypes.PublicMethods - )] - TProperty + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] TProperty >(Func getValue, Action setValue) : PropertyBinding( typeof(TProperty),