Parcourir la source

Rename BindingExtension->ReflectionBindingExtension.

Implement x:CompileBindings directive to toggle between transforming Binding->ReflectionBindingExtension and Binding->CompiledBindingExtension.

Expose a property on AvaloniaXamlIlCompiler to set the default transformation.
Jeremy Koritzinsky il y a 6 ans
Parent
commit
f9993e8980

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@@ -32,7 +32,7 @@
         <Compile Include="Converters\IconTypeConverter.cs" />
         <Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" />
         <Compile Include="Converters\PointsListTypeConverter.cs" />
-        <Compile Include="MarkupExtensions\BindingExtension.cs" />
+        <Compile Include="MarkupExtensions\ReflectionBindingExtension.cs" />
         <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
         <Compile Include="Properties\AssemblyInfo.cs" />
         <Compile Include="Styling\StyleInclude.cs" />
@@ -45,7 +45,7 @@
         <Compile Include="Templates\TreeDataTemplate.cs" />
         <Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
         <Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompilerConfiguration.cs" />
-        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionHackTransformer.cs" />
+        <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaBindingExtensionTransformer.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlAvaloniaPropertyResolver.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathParser.cs" />
         <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathTransformer.cs" />

+ 3 - 3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs → src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@@ -12,13 +12,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
     using Avalonia.Styling;
     using System.ComponentModel;
 
-    public class BindingExtension
+    public class ReflectionBindingExtension
     {
-        public BindingExtension()
+        public ReflectionBindingExtension()
         {
         }
 
-        public BindingExtension(string path)
+        public ReflectionBindingExtension(string path)
         {
             Path = path;
         }

+ 8 - 1
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs

@@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
         private readonly XamlIlTransformerConfiguration _configuration;
         private readonly IXamlIlType _contextType;
         private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
+        private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
 
         private AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration) : base(configuration, true)
         {
@@ -32,7 +33,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             Transformers.Insert(0, new XNameTransformer());
             Transformers.Insert(1, new IgnoredDirectivesTransformer());
             Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
-            Transformers.Insert(3, new AvaloniaBindingExtensionHackTransformer());
+            Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
             
             
             // Targeted
@@ -89,6 +90,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
             set => _designTransformer.IsDesignMode = value;
         }
 
+        public bool DefaultCompileBindings
+        {
+            get => _bindingTransformer.CompileBindingsByDefault;
+            set => _bindingTransformer.CompileBindingsByDefault = value;
+        }
+
         public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType)
         {
             var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary<string, string>

+ 0 - 20
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs

@@ -1,20 +0,0 @@
-using XamlIl.Ast;
-using XamlIl.Transform;
-
-namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
-{
-    class AvaloniaBindingExtensionHackTransformer : IXamlIlAstTransformer
-    {
-        public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
-        {
-            // Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
-            // This is the legacy of Portable.Xaml, so we emulate that behavior here
-
-            if (node is XamlIlAstXmlTypeReference tref
-                && tref.Name == "Binding"
-                && tref.XmlNamespace == "https://github.com/avaloniaui")
-                tref.IsMarkupExtension = true;
-            return node;
-        }
-    }
-}

+ 73 - 0
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs

@@ -0,0 +1,73 @@
+using System.Linq;
+using XamlIl;
+using XamlIl.Ast;
+using XamlIl.Transform;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
+{
+    class AvaloniaBindingExtensionTransformer : IXamlIlAstTransformer
+    {
+        public bool CompileBindingsByDefault { get; set; }
+
+        public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
+        {
+            if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlCompileBindingsNode)
+            {
+                return node;
+            }
+
+            if (node is XamlIlAstObjectNode obj)
+            {
+                foreach (var item in obj.Children)
+                {
+                    if (item is XamlIlAstXmlDirective directive)
+                    {
+                        if (directive.Namespace == XamlNamespaces.Xaml2006
+                            && directive.Name == "CompileBindings"
+                            && directive.Values.Count == 1)
+                        {
+                            if (!(directive.Values[0] is XamlIlAstTextNode text
+                                && bool.TryParse(text.Text, out var compileBindings)))
+                            {
+                                throw new XamlIlParseException("The value of x:CompileBindings must be a literal boolean value.", directive.Values[0]);
+                            }
+
+                            obj.Children.Remove(directive);
+
+                            return new AvaloniaXamlIlCompileBindingsNode(obj, compileBindings);
+                        }
+                    }
+                }
+            }
+
+            // Our code base expects XAML parser to prefer `FooExtension` to `Foo` even with `<Foo>` syntax
+            // This is the legacy of Portable.Xaml, so we emulate that behavior here
+
+            if (node is XamlIlAstXmlTypeReference tref
+                && tref.Name == "Binding"
+                && tref.XmlNamespace == "https://github.com/avaloniaui")
+            {
+                tref.IsMarkupExtension = true;
+
+                var compileBindings = context.ParentNodes()
+                    .OfType<AvaloniaXamlIlCompileBindingsNode>()
+                    .FirstOrDefault()
+                    ?.CompileBindings ?? CompileBindingsByDefault;
+
+                tref.Name = compileBindings ? "CompiledBinding" : "ReflectionBinding";
+            }
+            return node;
+        }
+    }
+
+    internal class AvaloniaXamlIlCompileBindingsNode : XamlIlValueWithSideEffectNodeBase
+    {
+        public AvaloniaXamlIlCompileBindingsNode(IXamlIlAstValueNode value, bool compileBindings)
+            : base(value, value)
+        {
+            CompileBindings = compileBindings;
+        }
+
+        public bool CompileBindings { get; }
+    }
+}

+ 11 - 7
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs

@@ -8,13 +8,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
     {
         public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
         {
-            if (node is NestedScopeMetadataNode nestedScope)
-                node = nestedScope.Value;
-
-            if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
-                node = dataContextType.Value;
-
-            return node;
+            while (true)
+            {
+                if (node is NestedScopeMetadataNode nestedScope)
+                    node = nestedScope.Value;
+                else if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
+                    node = dataContextType.Value;
+                else if (node is AvaloniaXamlIlCompileBindingsNode compileBindings)
+                    node = compileBindings.Value;
+                else
+                    return node;
+            }
         }
     }
 }

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@@ -144,7 +144,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(XamlIlAstTransformationContext context, XamlIlAstObjectNode on, XamlIlAstObjectNode obj)
         {
             var bindingType = context.GetAvaloniaTypes().IBinding;
-            if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().BindingExtension))
+            if (!bindingType.IsAssignableFrom(obj.Type.GetClrType()) && !obj.Type.GetClrType().Equals(context.GetAvaloniaTypes().ReflectionBindingExtension))
             {
                 return new AvaloniaXamlIlDataContextTypeMetadataNode(on, obj.Type.GetClrType());
             }

+ 2 - 2
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@@ -37,7 +37,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
         public IXamlIlType CompiledBindingExtension { get; }
         public IXamlIlType DataTemplate { get; }
         public IXamlIlType IItemsPresenterHost { get; }
-        public IXamlIlType BindingExtension { get; }
+        public IXamlIlType ReflectionBindingExtension { get; }
 
         public IXamlIlType RelativeSource { get; }
 
@@ -92,7 +92,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
             CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
             DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
             IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
-            BindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.BindingExtension");
+            ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");
             RelativeSource = cfg.TypeSystem.GetType("Avalonia.Data.RelativeSource");
         }
     }

+ 63 - 0
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@@ -519,6 +519,69 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
                 Assert.Equal("Test".Length.ToString(), target.Text);
             }
         }
+
+        [Fact]
+        public void CompilesBindingWhenRequested()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestDataContext'
+        x:CompileBindings='true'>
+    <TextBlock Text='{Binding StringProperty}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                var window = (Window)loader.Load(xaml);
+                var textBlock = window.FindControl<TextBlock>("textBlock");
+
+                var dataContext = new TestDataContext
+                {
+                    StringProperty = "foobar"
+                };
+
+                window.DataContext = dataContext;
+
+                Assert.Equal(dataContext.StringProperty, textBlock.Text);
+            }
+        }
+
+        [Fact]
+        public void ThrowsOnInvalidBindingPathOnCompiledBindingEnabledViaDirective()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestDataContext'
+        x:CompileBindings='true'>
+    <TextBlock Text='{Binding InvalidPath}' Name='textBlock' />
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlIlParseException>(() => loader.Load(xaml));
+            }
+        }
+
+        [Fact]
+        public void ThrowsOnInvalidCompileBindingsDirective()
+        {
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var xaml = @"
+<Window xmlns='https://github.com/avaloniaui'
+        xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
+        xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
+        x:DataType='local:TestDataContext'
+        x:CompileBindings='notabool'>
+</Window>";
+                var loader = new AvaloniaXamlLoader();
+                Assert.Throws<XamlIlParseException>(() => loader.Load(xaml));
+            }
+        }
     }
 
     public class TestDataContext