From f9993e8980a36ae4492167a77d40437f8f9585a2 Mon Sep 17 00:00:00 2001 From: Jeremy Koritzinsky Date: Tue, 15 Oct 2019 21:54:52 -0700 Subject: [PATCH] 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. --- .../Avalonia.Markup.Xaml.csproj | 4 +- ...nsion.cs => ReflectionBindingExtension.cs} | 6 +- .../AvaloniaXamlIlCompiler.cs | 9 ++- ...AvaloniaBindingExtensionHackTransformer.cs | 20 ----- .../AvaloniaBindingExtensionTransformer.cs | 73 +++++++++++++++++++ ...iaXamlIlCompiledBindingsMetadataRemover.cs | 18 +++-- ...valoniaXamlIlDataContextTypeTransformer.cs | 2 +- .../AvaloniaXamlIlWellKnownTypes.cs | 4 +- .../CompiledBindingExtensionTests.cs | 63 ++++++++++++++++ 9 files changed, 163 insertions(+), 36 deletions(-) rename src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/{BindingExtension.cs => ReflectionBindingExtension.cs} (94%) delete mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs create mode 100644 src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj index 2f3f8c4daa..00479919a3 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj +++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj @@ -32,7 +32,7 @@ - + @@ -45,7 +45,7 @@ - + diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs similarity index 94% rename from src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs rename to src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs index a466714136..470cdbd08f 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs +++ b/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; } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs index 2b09934057..b126834e5b 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/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 diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs deleted file mode 100644 index c89106312f..0000000000 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs +++ /dev/null @@ -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 `` 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; - } - } -} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs new file mode 100644 index 0000000000..23904a19fb --- /dev/null +++ b/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 `` 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() + .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; } + } +} diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs index b114b7476f..4a9e9fa3a7 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs +++ b/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; + } } } } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs index b558019b33..6c7dbbb861 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs +++ b/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()); } diff --git a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 829be2cb1a..6f2861e2bf 100644 --- a/src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/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"); } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs index 43aad921ff..0062093f4c 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs +++ b/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 = @" + + +"; + var loader = new AvaloniaXamlLoader(); + var window = (Window)loader.Load(xaml); + var textBlock = window.FindControl("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 = @" + + +"; + var loader = new AvaloniaXamlLoader(); + Assert.Throws(() => loader.Load(xaml)); + } + } + + [Fact] + public void ThrowsOnInvalidCompileBindingsDirective() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var xaml = @" + +"; + var loader = new AvaloniaXamlLoader(); + Assert.Throws(() => loader.Load(xaml)); + } + } } public class TestDataContext