Esempio n. 1
0
        private void ApppendSource(GeneratorExecutionContext context, StringBuilder sourceBuilder, GenerationDetails generateThis)
        {
            string propertyName  = generateThis.MethodNameNode.Identifier.ValueText;
            string dpMemberName  = propertyName + "Property";
            string dpkMemberName = propertyName + "PropertyKey";

            Accessibility dpAccess  = generateThis.FieldSymbol.DeclaredAccessibility;
            Accessibility dpkAccess = generateThis.FieldSymbol.DeclaredAccessibility;

            // If this is a DependencyPropertyKey, then we may need to create the corresponding DependencyProperty field.
            // We do this because it's proper to always have a DependencyProperty field & because the DependencyProperty
            // field is required when using TemplateBindings in XAML.
            if (generateThis.IsDpk)
            {
                ISymbol?dpMemberSymbol = generateThis.FieldSymbol.ContainingType.GetMembers(dpMemberName).FirstOrDefault();
                if (dpMemberSymbol != null)
                {
                    dpAccess = dpMemberSymbol.DeclaredAccessibility;
                }
                else
                {
                    dpAccess = Accessibility.Public;

                    // Something like...
                    //	public static readonly DependencyProperty FooProperty = FooPropertyKey.DependencyProperty;
                    sourceBuilder.Append($@"
		public static readonly DependencyProperty {dpMemberName} = {dpkMemberName}.DependencyProperty;"        );
                }
            }

            // Try to get the generic type argument (if it exists, this will be the type of the property).
            GeneratorOps.TryGetGenericTypeArgument(context, generateThis.MethodNameNode, out ITypeSymbol? genTypeArg);

            // We support 0, 1, or 2 arguments. Check for default value and/or flags arguments.
            //	(A) Gen.Foo<T>()
            //	(B) Gen.Foo(defaultValue)
            //	(C) Gen.Foo<T>(flags)
            //	(D) Gen.Foo(defaultValue, flags)
            // The first argument is either the default value or the flags.
            // Note: We do not support properties whose default value is `FrameworkPropertyMetadataOptions` because
            // it's a niche case that would add code complexity.
            ArgumentSyntax?defaultValueArgNode = null;
            ITypeSymbol?   typeOfFirstArg      = null;
            bool           hasFlags            = false;

            if (GeneratorOps.TryGetAncestor(generateThis.MethodNameNode, out InvocationExpressionSyntax? invocationExpressionNode))
            {
                var args = invocationExpressionNode.ArgumentList.Arguments;
                if (args.Count > 0)
                {
                    // If the first argument is the flags, then we generate (C); otherwise, we generate (B) or (D).
                    typeOfFirstArg = GetArgumentType(context, args[0]) ?? this.objTypeSymbol;
                    if (typeOfFirstArg.Equals(this.flagsTypeSymbol, SymbolEqualityComparer.Default))
                    {
                        hasFlags = true;
                    }
                    else
                    {
                        defaultValueArgNode = args[0];
                        hasFlags            = args.Count > 1;
                    }
                }
            }

            bool hasDefaultValue = defaultValueArgNode != null;

            // Determine the type of the property.
            // If there is a generic type argument, then use that; otherwise, use the type of the default value argument.
            // As a safety precaution - ensure that the generated code is always valid by defaulting to use `object`.
            // But really, if we were unable to get the type, that means the user's code doesn't compile anyhow.
            generateThis.PropertyType =
                genTypeArg
                ?? (hasDefaultValue ? typeOfFirstArg : null)
                ?? this.objTypeSymbol;

            generateThis.PropertyTypeName = generateThis.PropertyType.ToDisplayString();

            string genClassDecl;
            string?moreDox = null;

            if (generateThis.IsAttached)
            {
                string targetTypeName = "DependencyObject";

                if (generateThis.MethodNameNode.Parent is MemberAccessExpressionSyntax memberAccessExpr &&
                    memberAccessExpr.Expression is GenericNameSyntax genClassNameNode)
                {
                    genClassDecl = "GenAttached<__TTarget> where __TTarget : DependencyObject";

                    if (GeneratorOps.TryGetGenericTypeArgument(context, genClassNameNode, out ITypeSymbol? attachmentNarrowingType))
                    {
                        generateThis.AttachmentNarrowingType = attachmentNarrowingType;
                        targetTypeName = attachmentNarrowingType.ToDisplayString();
                        moreDox        = $@"<br/>This attached property is only for use with objects of type <typeparamref name=""__TTarget""/>.";
                    }
                }
                else
                {
                    genClassDecl = "GenAttached";
                }

                // Write the static get/set methods source code.
                string getterAccess = dpAccess.ToString().ToLower();
                string setterAccess = generateThis.IsDpk ? dpkAccess.ToString().ToLower() : getterAccess;
                string setterArg0   = generateThis.IsDpk ? dpkMemberName : dpMemberName;

                // Something like...
                //	public static int GetFoo(DependencyObject d) => (int)d.GetValue(FooProperty);
                //	private static void SetFoo(DependencyObject d, int value) => d.SetValue(FooPropertyKey);
                sourceBuilder.Append($@"
		{getterAccess} static {generateThis.PropertyTypeName} Get{propertyName}({targetTypeName} d) => ({generateThis.PropertyTypeName})d.GetValue({dpMemberName});
		{setterAccess} static void Set{propertyName}({targetTypeName} d, {generateThis.PropertyTypeName} value) => d.SetValue({setterArg0}, value);"        );
            }
Esempio n. 2
0
        public void Execute(GeneratorExecutionContext context)
        {
            //DebugMe.Go();

            this.useNullableContext = (context.ParseOptions as CSharpParseOptions)?.LanguageVersion >= LanguageVersion.CSharp8;

            var syntaxReceiver = (SyntaxReceiver)context.SyntaxReceiver !;

            // Cast keys to `ISymbol` in the key selector to make the analyzer shutup about CS8602 ("Dereference of a possibly null reference.").
            var namespaces = UpdateAndFilterGenerationRequests(context, syntaxReceiver.GenerationRequests)
                             .GroupBy(g => (ISymbol)g.FieldSymbol.ContainingType, SymbolEqualityComparer.Default)
                             .GroupBy(g => (ISymbol)g.Key.ContainingNamespace, SymbolEqualityComparer.Default);

            StringBuilder sourceBuilder = new();

            foreach (var namespaceGroup in namespaces)
            {
                // Get these type symbols now so we don't waste time finding them each time we need them later.
                this.objTypeSymbol ??= context.Compilation.GetTypeByMetadataName("System.Object") !;
                this.doTypeSymbol ??= context.Compilation.GetTypeByMetadataName("System.Windows.DependencyObject") !;
                this.argsTypeSymbol ??= context.Compilation.GetTypeByMetadataName("System.Windows.DependencyPropertyChangedEventArgs") !;
                this.flagsTypeSymbol ??= context.Compilation.GetTypeByMetadataName("System.Windows.FrameworkPropertyMetadataOptions");
                this.reTypeSymbol ??= context.Compilation.GetTypeByMetadataName("System.Windows.RoutedEvent");

                string namespaceName = namespaceGroup.Key.ToString();
                sourceBuilder.Append($@"
namespace {namespaceName}
{{");

                foreach (var classGroup in namespaceGroup)
                {
                    string?maybeStatic = classGroup.Key.IsStatic ? "static " : null;
                    string className   = GeneratorOps.GetTypeName((INamedTypeSymbol)classGroup.Key);
                    sourceBuilder.Append($@"
	{maybeStatic}partial class {className}
	{{"    );

                    foreach (var generateThis in classGroup)
                    {
                        context.CancellationToken.ThrowIfCancellationRequested();

                        this.ApppendSource(context, sourceBuilder, generateThis);
                    }

                    sourceBuilder.Append(@"
	}
");
                }

                sourceBuilder.Append(@"
}
");
            }

            if (sourceBuilder.Length != 0)
            {
                string?maybeNullableContext = this.useNullableContext ? "#nullable enable" : null;

                sourceBuilder.Insert(0,
                                     $@"//------------------------------------------------------------------------------
// <auto-generated>
//     This code was generated by a boilerplatezero (BPZ) source generator.
//     Generator = {this.GetType().FullName}
//     {Diagnostics.HelpLinkUri}
// </auto-generated>
//------------------------------------------------------------------------------
{maybeNullableContext}
using System.Windows;
");

                context.AddSource($"bpz.DependencyProperties.g.cs", sourceBuilder.ToString());
            }
        }
        private void ApppendSource(GeneratorExecutionContext context, StringBuilder sourceBuilder, GenerationDetails generateThis)
        {
            string eventName             = generateThis.MethodNameNode.Identifier.ValueText;
            string routedEventMemberName = generateThis.FieldSymbol.Name;

            string eventHandlerTypeDoxString = $@"<see cref=""{this.rehTypeSymbol.ToDisplayString()}""/>";

            // Try to get the generic type argument (if it exists, this will be the type of the event handler).
            if (GeneratorOps.TryGetGenericTypeArgument(context, generateThis.MethodNameNode, out ITypeSymbol? genTypeArg))
            {
                // If the type is a multicast delegate, then use it;
                // otherwise, use the type in a `RoutedPropertyChangedEventHandler<>`.
                if (genTypeArg.BaseType?.Equals(this.mdTypeSymbol, SymbolEqualityComparer.Default) ?? false)
                {
                    // Good to go! Documentation can reference the generic type parameter.
                    eventHandlerTypeDoxString = @"<typeparamref name=""__T""/>";
                }
                else
                {
                    // Example: Transform `double` into `RoutedPropertyChangedEventHandler<double>`.
                    genTypeArg = this.rpcehTypeSymbol.Construct(genTypeArg);

                    // Documentation will appear as something like...
                    //	RoutedPropertyChangedEventHandler<T> of double
                    string rpcehT = GeneratorOps.ReplaceBrackets(this.rpcehTypeSymbol.ToDisplayString());
                    eventHandlerTypeDoxString = $@"<see cref=""{rpcehT}""/> of <typeparamref name=""__T""/>";
                }
            }

            // Determine the type of the handler.
            // If there is a generic type argument, then use that; otherwise, use `RoutedEventHandler`.
            generateThis.EventHandlerType     = genTypeArg ?? this.rehTypeSymbol;
            generateThis.EventHandlerTypeName = generateThis.EventHandlerType.ToDisplayString();

            string genClassDecl;
            string?moreDox = null;

            if (generateThis.IsAttached)
            {
                string targetTypeName   = "DependencyObject";
                string callerExpression = "(d as UIElement)?";

                if (generateThis.MethodNameNode.Parent is MemberAccessExpressionSyntax memberAccessExpr &&
                    memberAccessExpr.Expression is GenericNameSyntax genClassNameNode)
                {
                    genClassDecl = "GenAttached<__TTarget> where __TTarget : DependencyObject";

                    if (GeneratorOps.TryGetGenericTypeArgument(context, genClassNameNode, out ITypeSymbol? attachmentNarrowingType))
                    {
                        generateThis.AttachmentNarrowingType = attachmentNarrowingType;
                        targetTypeName   = attachmentNarrowingType.ToDisplayString();
                        callerExpression = "d";
                        moreDox          = $@"<br/>This attached event is only for use with objects of type <typeparamref name=""__TTarget""/>.";
                    }
                }
                else
                {
                    genClassDecl = "GenAttached";
                }

                // Write the static get/set methods source code.
                string methodsAccess = generateThis.FieldSymbol.DeclaredAccessibility.ToString().ToLower();

                // Something like...
                //	public static void AddFooChangedHandler(DependencyObject d, RoutedPropertyChangedEventHandler<int> handler) => (d as UIElement)?.AddHandler(FooChangedEvent, handler);
                //	public static void RemoveFooChangedHandler(DependencyObject d, RoutedPropertyChangedEventHandler<int> handler) => (d as UIElement)?.RemoveHandler(FooChangedEvent, handler);
                sourceBuilder.Append($@"
		{methodsAccess} static void Add{eventName}Handler({targetTypeName} d, {generateThis.EventHandlerTypeName} handler) => {callerExpression}.AddHandler({routedEventMemberName}, handler);
		{methodsAccess} static void Remove{eventName}Handler({targetTypeName} d, {generateThis.EventHandlerTypeName} handler) => {callerExpression}.RemoveHandler({routedEventMemberName}, handler);"        );
            }