Browse 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.
pull/2734/head
Jeremy Koritzinsky 7 years ago
parent
commit
f9993e8980
  1. 4
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  2. 6
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  3. 9
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlCompiler.cs
  4. 20
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionHackTransformer.cs
  5. 73
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaBindingExtensionTransformer.cs
  6. 18
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlCompiledBindingsMetadataRemover.cs
  7. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  8. 4
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  9. 63
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

4
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" />

6
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;
}

9
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>

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

18
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;
}
}
}
}

2
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());
}

4
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
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

Loading…
Cancel
Save