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\IconTypeConverter.cs" />
<Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" /> <Compile Include="Converters\AvaloniaPropertyTypeConverter.cs" />
<Compile Include="Converters\PointsListTypeConverter.cs" /> <Compile Include="Converters\PointsListTypeConverter.cs" />
<Compile Include="MarkupExtensions\BindingExtension.cs" /> <Compile Include="MarkupExtensions\ReflectionBindingExtension.cs" />
<Compile Include="MarkupExtensions\RelativeSourceExtension.cs" /> <Compile Include="MarkupExtensions\RelativeSourceExtension.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Styling\StyleInclude.cs" /> <Compile Include="Styling\StyleInclude.cs" />
@ -45,7 +45,7 @@
<Compile Include="Templates\TreeDataTemplate.cs" /> <Compile Include="Templates\TreeDataTemplate.cs" />
<Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" /> <Compile Include="XamlIl\AvaloniaXamlIlRuntimeCompiler.cs" />
<Compile Include="XamlIl\CompilerExtensions\AvaloniaXamlIlCompilerConfiguration.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\AvaloniaXamlIlAvaloniaPropertyResolver.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathParser.cs" /> <Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathParser.cs" />
<Compile Include="XamlIl\CompilerExtensions\Transformers\AvaloniaXamlIlBindingPathTransformer.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 Avalonia.Styling;
using System.ComponentModel; using System.ComponentModel;
public class BindingExtension public class ReflectionBindingExtension
{ {
public BindingExtension() public ReflectionBindingExtension()
{ {
} }
public BindingExtension(string path) public ReflectionBindingExtension(string path)
{ {
Path = 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 XamlIlTransformerConfiguration _configuration;
private readonly IXamlIlType _contextType; private readonly IXamlIlType _contextType;
private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer; private readonly AvaloniaXamlIlDesignPropertiesTransformer _designTransformer;
private readonly AvaloniaBindingExtensionTransformer _bindingTransformer;
private AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration) : base(configuration, true) private AvaloniaXamlIlCompiler(AvaloniaXamlIlCompilerConfiguration configuration) : base(configuration, true)
{ {
@ -32,7 +33,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transformers.Insert(0, new XNameTransformer()); Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer()); Transformers.Insert(1, new IgnoredDirectivesTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer()); Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(3, new AvaloniaBindingExtensionHackTransformer()); Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
// Targeted // Targeted
@ -89,6 +90,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
set => _designTransformer.IsDesignMode = value; 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) public void ParseAndCompile(string xaml, string baseUri, IFileSource fileSource, IXamlIlTypeBuilder tb, IXamlIlType overrideRootType)
{ {
var parsed = XDocumentXamlIlParser.Parse(xaml, new Dictionary<string, string> 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) public IXamlIlAstNode Transform(XamlIlAstTransformationContext context, IXamlIlAstNode node)
{ {
if (node is NestedScopeMetadataNode nestedScope) while (true)
node = nestedScope.Value; {
if (node is NestedScopeMetadataNode nestedScope)
if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType) node = nestedScope.Value;
node = dataContextType.Value; else if (node is AvaloniaXamlIlDataContextTypeMetadataNode dataContextType)
node = dataContextType.Value;
return node; 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) private static AvaloniaXamlIlDataContextTypeMetadataNode ParseDataContext(XamlIlAstTransformationContext context, XamlIlAstObjectNode on, XamlIlAstObjectNode obj)
{ {
var bindingType = context.GetAvaloniaTypes().IBinding; 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()); 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 CompiledBindingExtension { get; }
public IXamlIlType DataTemplate { get; } public IXamlIlType DataTemplate { get; }
public IXamlIlType IItemsPresenterHost { get; } public IXamlIlType IItemsPresenterHost { get; }
public IXamlIlType BindingExtension { get; } public IXamlIlType ReflectionBindingExtension { get; }
public IXamlIlType RelativeSource { 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"); CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate"); DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost"); 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"); 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); 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 public class TestDataContext

Loading…
Cancel
Save