From 01a7c859d4c4d46a23eb4c152217c106cd9318bb Mon Sep 17 00:00:00 2001 From: Julien Lebosquain Date: Fri, 24 Jan 2025 10:07:04 +0100 Subject: [PATCH] Fix style without selector not finding target type (#18026) * Add failing style test without selector * Fix XAML target type of style without selector * Address review * Throw for style without selector in ControlTheme --- .../AvaloniaXamlIlSelectorTransformer.cs | 55 +++++++++++++++++++ .../AvaloniaXamlIlWellKnownTypes.cs | 2 + .../Xaml/StyleTests.cs | 52 +++++++++++++++++- 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs index cfec681558..e0d234e6df 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs @@ -32,8 +32,26 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers var pn = on.Children.OfType() .FirstOrDefault(p => p.Property.GetClrProperty().Name == "Selector"); + // Missing selector, use the object's target type if available if (pn == null) + { + // We already went through this node + if (context.ParentNodes().FirstOrDefault() is AvaloniaXamlIlTargetTypeMetadataNode metadataNode + && metadataNode.Value == on) + { + return node; + } + + if (FindStyleParentObject(on, context) is { } parentObjectNode) + { + return new AvaloniaXamlIlTargetTypeMetadataNode( + on, + new XamlAstClrTypeReference(node, parentObjectNode.Type.GetClrType(), false), + AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + return node; + } if (pn.Values.Count != 1) throw new XamlSelectorsTransformException("Selector property should have exactly one value", @@ -195,6 +213,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers pn.Values[0] = selector; var templateType = GetLastTemplateTypeFromSelector(selector); + + // Empty selector, use the object's target type if available + if (selector == initialNode) + { + if (FindStyleParentObject(on, context) is { } parentObjectNode) + { + return new AvaloniaXamlIlTargetTypeMetadataNode( + on, + new XamlAstClrTypeReference(node, parentObjectNode.Type.GetClrType(), false), + AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style); + } + + return node; + } var styleNode = new AvaloniaXamlIlTargetTypeMetadataNode(on, new XamlAstClrTypeReference(selector, selector.TargetType!, false), @@ -209,6 +241,29 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers }; } + private static XamlAstObjectNode? FindStyleParentObject(XamlAstNode styleNode, AstTransformationContext context) + { + var avaloniaTypes = context.GetAvaloniaTypes(); + + var parentNode = context + .ParentNodes() + .OfType() + .FirstOrDefault(n => !avaloniaTypes.Styles.IsAssignableFrom(n.Type.GetClrType())); + + if (parentNode is not null) + { + var parentType = parentNode.Type.GetClrType(); + + if (avaloniaTypes.StyledElement.IsAssignableFrom(parentType)) + return parentNode; + + if (avaloniaTypes.ControlTheme.IsAssignableFrom(parentType)) + throw new XamlTransformException("Cannot add a Style without selector to a ControlTheme.", styleNode); + } + + return null; + } + private static IXamlType? GetLastTemplateTypeFromSelector(XamlIlSelectorNode? node) { while (node is not null) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs index 25c0135492..ed57fd008b 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs @@ -126,6 +126,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers public IXamlType UriKind { get; } public IXamlConstructor UriConstructor { get; } public IXamlType Style { get; } + public IXamlType Styles { get; } public IXamlType ControlTheme { get; } public IXamlType WindowTransparencyLevel { get; } public IXamlType IReadOnlyListOfT { get; } @@ -325,6 +326,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers UriKind = cfg.TypeSystem.GetType("System.UriKind"); UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind }); Style = cfg.TypeSystem.GetType("Avalonia.Styling.Style"); + Styles = cfg.TypeSystem.GetType("Avalonia.Styling.Styles"); ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme"); ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate"); IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1"); diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs index af12c58f26..23bd85be79 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/StyleTests.cs @@ -15,6 +15,7 @@ using Xunit; namespace Avalonia.Markup.Xaml.UnitTests.Xaml { + [InvariantCulture] public class StyleTests : XamlTestBase { [Fact] @@ -713,9 +714,6 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml [Fact] public void Fails_Use_Classes_In_Setter_When_Selector_Is_Complex() { - // XmlException contains culture specific position message - Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; - using (UnitTestApplication.Start(TestServices.StyledWindow)) { var xaml = $""" @@ -739,5 +737,53 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml Assert.Equal ("Cannot set Classes Binding property '(Classes.Banned)' because the style has an activator. Line 6, position 14.", exception.Message); } } + + [Theory] + [InlineData("")] + [InlineData("")] + [InlineData("")] + [InlineData("")] + public void No_Selector_Should_Target_Parent_Type(string styleStart, string styleEnd) + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow); + + var window = (Window)AvaloniaRuntimeXamlLoader.Load( + $""" + + + {styleStart} + + {styleEnd} + + + """); + + Assert.Equal("title set via style!", window.Title); + } + + + [Theory] + [InlineData("")] + [InlineData("")] + public void No_Selector_Should_Fail_In_Control_Theme(string styleStart, string styleEnd) + { + using var app = UnitTestApplication.Start(TestServices.StyledWindow); + + var exception = Assert.ThrowsAny(() => (Window)AvaloniaRuntimeXamlLoader.Load( + $$""" + + + + {{styleStart}} + + {{styleEnd}} + + + + """)); + + Assert.Equal("Cannot add a Style without selector to a ControlTheme. Line 5, position 14.", exception.Message); + } } }