public IXamlAstNode Transform()

in src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs [17:207]


    public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
    {
        if (node is XamlMarkupExtensionNode
            {
                Value: XamlAstObjectNode { Type: XamlAstClrTypeReference { Type: { } type } } objectNode
            } markupExtensionNode
            && type.FindMethods(m => m.IsPublic && m.Parameters.Count is 1 or 2 && m.ReturnType == context.Configuration.WellKnownTypes.Boolean && m.Name == "ShouldProvideOption").ToArray() is { } methods
            && methods.Any())
        {
            var optionAttribute = context.GetAvaloniaTypes().MarkupExtensionOptionAttribute;
            var defaultOptionAttribute = context.GetAvaloniaTypes().MarkupExtensionDefaultOptionAttribute;

            var typeArgument = type.GenericArguments.FirstOrDefault();

            IXamlAstValueNode? defaultValue = null;
            var values = new List<OptionsMarkupExtensionBranch>();

            if (objectNode.Arguments.FirstOrDefault() is { } argument)
            {
                var hasDefaultProp = objectNode.Type.GetClrType().GetAllProperties().Any(p =>
                    p.CustomAttributes.Any(a => a.Type == defaultOptionAttribute));
                if (hasDefaultProp)
                {
                    if (objectNode.Arguments.Count > 1)
                    {
                        throw new XamlTransformException("Options MarkupExtensions allow only single argument", objectNode);
                    }

                    defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
                    objectNode.Arguments.Remove(argument);
                }
            }

            foreach (var extProp in objectNode.Children.OfType<XamlAstXamlPropertyValueNode>().ToArray())
            {
                if (!extProp.Values.Any())
                {
                    continue;
                }

                var shouldRemoveProp = false;
                var onObjs = extProp.Values.OfType<XamlAstObjectNode>()
                    .Where(o => o.Type.GetClrType() == context.GetAvaloniaTypes().OnExtensionType).ToArray();
                if (onObjs.Any())
                {
                    shouldRemoveProp = true;
                    foreach (var onObj in onObjs)
                    {
                        var optionsPropNode = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
                            .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Options")
                            ?.Values.Single();
                        var options = (optionsPropNode as XamlAstTextNode)?.Text.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
                            ?? Array.Empty<string>();
                        if (options.Length == 0)
                        {
                            throw new XamlTransformException("On.Options string must be set", onObj);
                        }

                        var content = onObj.Children.OfType<XamlAstXamlPropertyValueNode>()
                            .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
                        if (content is null)
                        {
                            throw new XamlTransformException("On content object must be set", onObj);
                        }

                        var propertiesSet = options
                            .Select(o => type.GetAllProperties()
                                .FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
                                ?? throw new XamlTransformException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
                            .ToArray();
                        foreach (var propertySet in propertiesSet)
                        {
                            AddBranchNode(content.Values, propertySet.CustomAttributes, content);
                        }
                    }
                }
                else
                {
                    shouldRemoveProp = AddBranchNode(extProp.Values, extProp.Property.GetClrProperty().CustomAttributes, extProp);
                }

                if (shouldRemoveProp)
                {
                    objectNode.Children.Remove(extProp);
                }
            }

            if (defaultValue is null && !values.Any())
            {
                throw new XamlTransformException("Options markup extension requires at least one option to be set", objectNode);
            }

            return new OptionsMarkupExtensionNode(
                markupExtensionNode, values.ToArray(), defaultValue,
                context.Configuration.TypeMappings.ServiceProvider);

            bool AddBranchNode(
                IReadOnlyCollection<IXamlAstValueNode> valueNodes,
                IReadOnlyCollection<IXamlCustomAttribute> propAttributes,
                IXamlLineInfo li)
            {
                var transformed = TransformNode(valueNodes, typeArgument, li);
                if (propAttributes.FirstOrDefault(a => a.Type == defaultOptionAttribute) is { } defAttr)
                {
                    defaultValue = transformed;
                    return true;
                }
                else if (propAttributes.FirstOrDefault(a => a.Type == optionAttribute) is { } optAttr)
                {
                    var option = optAttr.Parameters.Single();
                    if (option is null)
                    {
                        throw new XamlTransformException("MarkupExtension option must not be null", li);
                    }

                    var optionAsString = option.ToString() ?? string.Empty;
                    IXamlAstValueNode? optionNode = null;
                    foreach (var method in methods)
                    {
                        try
                        {
                            var targetType = method.Parameters.Last();
                            if (targetType.FullName == "System.Type")
                            {
                                if (option is IXamlType typeOption)
                                {
                                    optionNode = new XamlTypeExtensionNode(li,
                                        new XamlAstClrTypeReference(li, typeOption, false), targetType);
                                }
                            }
                            else if (targetType == context.Configuration.WellKnownTypes.String)
                            {
                                optionNode = new XamlConstantNode(li, targetType, optionAsString);
                            }
                            else if (targetType.IsEnum)
                            {
                                if (TypeSystemHelpers.TryGetEnumValueNode(targetType, optionAsString, li, false,
                                        out var enumConstantNode))
                                {
                                    optionNode = enumConstantNode;
                                }
                            }
                            else if (TypeSystemHelpers.ParseConstantIfTypeAllows(optionAsString, targetType, li,
                                         out var constantNode))
                            {
                                optionNode = constantNode;
                            }
                        }
                        catch (FormatException)
                        {
                            // try next method overload
                        }

                        if (optionNode is not null)
                        {
                            values.Add(new OptionsMarkupExtensionBranch(optionNode, transformed, method));
                            return true;
                        }
                    }

                    throw new XamlTransformException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
                }

                return false;
            }
        }

        return node;

        IXamlAstValueNode TransformNode(
            IReadOnlyCollection<IXamlAstValueNode> values,
            IXamlType? suggestedType,
            IXamlLineInfo line)
        {
            if (suggestedType is not null)
            {
                values = values
                    .Select(v => XamlTransformHelpers
                        .TryGetCorrectlyTypedValue(context, v, suggestedType, out var converted)
                        ? converted : v)
                    .ToArray();
            }

            if (values.Count > 1)
            {
                throw new XamlTransformException("Options markup extension supports only a singular value", line);
            }

            return values.Single();
        }
    }