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);" ); }
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);" ); }