diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs index aaaee39b0d..197815f9a0 100644 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs @@ -49,6 +49,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions InsertBefore( new AvaloniaXamlIlControlThemeTransformer(), new AvaloniaXamlIlSelectorTransformer(), + new AvaloniaXamlIlDuplicateSettersChecker(), new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(), new AvaloniaXamlIlBindingPathParser(), new AvaloniaXamlIlPropertyPathTransformer(), diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs new file mode 100644 index 0000000000..4f92fde3f0 --- /dev/null +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDuplicateSettersChecker.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Linq; +using XamlX; +using XamlX.Ast; +using XamlX.Transform; + +namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers; + +class AvaloniaXamlIlDuplicateSettersChecker : IXamlAstTransformer +{ + public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node) + { + if (node is not XamlAstObjectNode objectNode) + { + return node; + } + + var fullName = objectNode.Type.GetClrType().FullName; + if (fullName is not ("Avalonia.Styling.Style" or "Avalonia.Styling.ControlTheme")) + { + return node; + } + + var properties = objectNode.Children + .OfType() + .Where(n => n.Type.GetClrType().Name == "Setter") + .SelectMany(setter => + setter.Children.OfType() + .Where(c => c.Property.GetClrProperty().Name == "Property")) + .Select(p => p.Values[0]) + .OfType() + .Select(x => x.Text); + var index = new HashSet(); + foreach (var property in properties) + { + if (!index.Add(property)) + { + throw new XamlParseException($"Duplicate setter encountered for property '{property}'", node); + } + } + + return node; + } +} diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs index f42f787117..be2cae8ec4 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/XamlIlTests.cs @@ -334,6 +334,47 @@ namespace Avalonia.Markup.Xaml.UnitTests var parsed = (Button)AvaloniaRuntimeXamlLoader.Load(document); Assert.Equal(Colors.Blue, ((ISolidColorBrush)parsed.Background!).Color); } + + [Fact] + public void Style_Parser_Throws_For_Duplicate_Setter() + { + var xaml = @" + + + + + +"; + AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true), + e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'")); + } + + [Fact] + public void Control_Theme_Parser_Throws_For_Duplicate_Setter() + { + var xaml = @" + + + + + + + + + + +"; + AssertThrows(() => AvaloniaRuntimeXamlLoader.Load(xaml, typeof(XamlIlTests).Assembly, designMode: true), + e => e.Message.StartsWith("Duplicate setter encountered for property 'Height'")); + } } public class XamlIlBugTestsEventHandlerCodeBehind : Window