From 2128f302abb14bcb001379a5e2e21bf686269a6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Miku=C5=A1?= Date: Thu, 4 Jan 2024 14:35:05 +0100 Subject: [PATCH 1/6] test for attribute initializer --- src/Tests/AssertEx.cs | 29 +++++++++++++++++++ .../Parser/Binding/BindingParserTests.cs | 15 +--------- .../PropertyDirectiveTests.cs | 5 ++++ 3 files changed, 35 insertions(+), 14 deletions(-) create mode 100644 src/Tests/AssertEx.cs diff --git a/src/Tests/AssertEx.cs b/src/Tests/AssertEx.cs new file mode 100644 index 0000000000..bb56518dd6 --- /dev/null +++ b/src/Tests/AssertEx.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using DotVVM.Framework.Compilation.Parser.Binding.Parser; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace DotVVM.Framework.Tests +{ + public static class AssertEx + { + public static void AssertNode(BindingParserNode node, string expectedDisplayString, int start, int length, bool hasErrors = false) + { + Assert.AreEqual(expectedDisplayString, node.ToDisplayString(), $"Node {node.GetType().Name}: display string incorrect."); + Assert.AreEqual(start, node.StartPosition, $"Node {node.GetType().Name}: Start position incorrect."); + Assert.AreEqual(length, node.Length, $"Node {node.GetType().Name}: Length incorrect."); + + if (hasErrors) + { + Assert.IsTrue(node.HasNodeErrors); + } + else + { + Assert.IsFalse(node.HasNodeErrors); + } + } + } +} diff --git a/src/Tests/Parser/Binding/BindingParserTests.cs b/src/Tests/Parser/Binding/BindingParserTests.cs index 65413f5612..52f7a12386 100644 --- a/src/Tests/Parser/Binding/BindingParserTests.cs +++ b/src/Tests/Parser/Binding/BindingParserTests.cs @@ -1490,20 +1490,7 @@ public void BindingParser_GenericMethodCall_MultipleGenericArguments() } private static void AssertNode(BindingParserNode node, string expectedDisplayString, int start, int length, bool hasErrors = false) - { - Assert.AreEqual(expectedDisplayString, node.ToDisplayString(), $"Node {node.GetType().Name}: display string incorrect."); - Assert.AreEqual(start, node.StartPosition, $"Node {node.GetType().Name}: Start position incorrect."); - Assert.AreEqual(length, node.Length, $"Node {node.GetType().Name}: Length incorrect."); - - if (hasErrors) - { - Assert.IsTrue(node.HasNodeErrors); - } - else - { - Assert.IsFalse(node.HasNodeErrors); - } - } + => AssertEx.AssertNode(node, expectedDisplayString, start, length, hasErrors); private static string SkipWhitespaces(string str) => string.Join("", str.Where(c => !char.IsWhiteSpace(c))); diff --git a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs index 21330ae2be..56c2231d38 100644 --- a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs +++ b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs @@ -22,6 +22,11 @@ @viewModel object Assert.AreEqual("MyProperty", property.NameSyntax.Name); Assert.AreEqual(4, property.Attributes.Count); + + AssertEx.AssertNode(property.Attributes[0].Initializer, "", 19, 0); + AssertEx.AssertNode(property.Attributes[1].Initializer, "", 28, 0); + AssertEx.AssertNode(property.Attributes[2].Initializer, "", 43, 0); + AssertEx.AssertNode(property.Attributes[3].Initializer, "t", 105, 0); } [TestMethod] From 212098895b80e6346afcfafb7df8ac96f35010d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Miku=C5=A1?= Date: Thu, 4 Jan 2024 23:22:16 +0100 Subject: [PATCH 2/6] Initializer rewritten to support half written values --- .../IAbstractDirectiveAttributeReference.cs | 2 +- .../Compilation/ControlTree/IAbstractTreeBuilder.cs | 2 +- .../Resolved/ResolvedPropertyDeclarationDirective.cs | 2 +- .../ResolvedPropertyDirectiveAttributeReference.cs | 4 ++-- .../ControlTree/Resolved/ResolvedTreeBuilder.cs | 2 +- .../PropertyDeclarationDirectiveCompiler.cs | 9 ++++----- .../PropertyDirectiveTests.cs | 12 ++++++++---- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractDirectiveAttributeReference.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractDirectiveAttributeReference.cs index fb651ef4c1..775cbb69da 100644 --- a/src/Framework/Framework/Compilation/ControlTree/IAbstractDirectiveAttributeReference.cs +++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractDirectiveAttributeReference.cs @@ -15,7 +15,7 @@ public interface IAbstractDirectiveAttributeReference { TypeReferenceBindingParserNode TypeSyntax { get; } IdentifierNameBindingParserNode NameSyntax { get; } - LiteralExpressionBindingParserNode Initializer { get; } + BindingParserNode Initializer { get; } ITypeDescriptor? Type { get; } } } diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs index 32f2edaac7..d94cfbb444 100644 --- a/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs +++ b/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs @@ -29,7 +29,7 @@ public interface IAbstractTreeBuilder IAbstractViewModuleDirective BuildViewModuleDirective(DothtmlDirectiveNode directiveNode, string modulePath, string resourceName); IAbstractPropertyDeclarationDirective BuildPropertyDeclarationDirective(DothtmlDirectiveNode directive, TypeReferenceBindingParserNode typeSyntax, SimpleNameBindingParserNode nameSyntax, BindingParserNode? initializer, IList resolvedAttributes, BindingParserNode valueSyntaxRoot, ImmutableList imports); - IAbstractDirectiveAttributeReference BuildPropertyDeclarationAttributeReference(DothtmlDirectiveNode directiveNode, IdentifierNameBindingParserNode propertyNameSyntax, TypeReferenceBindingParserNode typeSyntax, LiteralExpressionBindingParserNode initializer, ImmutableList imports); + IAbstractDirectiveAttributeReference BuildPropertyDeclarationAttributeReference(DothtmlDirectiveNode directiveNode, IdentifierNameBindingParserNode propertyNameSyntax, TypeReferenceBindingParserNode typeSyntax, BindingParserNode initializer, ImmutableList imports); IAbstractPropertyBinding BuildPropertyBinding(IPropertyDescriptor property, IAbstractBinding binding, DothtmlAttributeNode? sourceAttributeNode); IAbstractPropertyControl BuildPropertyControl(IPropertyDescriptor property, IAbstractControl? control, DothtmlElementNode? wrapperElementNode); diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs index 33212d3832..2bb7cf3ff7 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs @@ -62,7 +62,7 @@ private IEnumerable InstantiateAttributes(DothtmlDirectiveNode dothtmlDi (name, attributes) => { var attributeType = (attributes.First().Type as ResolvedTypeDescriptor)?.Type; - var properties = attributes.Select(a => (name: a.NameSyntax.Name, value: a.Initializer.Value)); + var properties = attributes.Select(a => (name: a.NameSyntax.Name, value: a.Initializer)); return (attributeType, properties); }).ToList(); diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDirectiveAttributeReference.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDirectiveAttributeReference.cs index 652789071f..bf12ce5b2d 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDirectiveAttributeReference.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDirectiveAttributeReference.cs @@ -15,14 +15,14 @@ public class ResolvedPropertyDirectiveAttributeReference : IAbstractDirectiveAtt public TypeReferenceBindingParserNode TypeSyntax { get; } public IdentifierNameBindingParserNode NameSyntax { get; } public ITypeDescriptor? Type { get; set; } - public LiteralExpressionBindingParserNode Initializer { get; } + public BindingParserNode Initializer { get; } public ResolvedPropertyDirectiveAttributeReference( DirectiveCompilationService directiveService, DothtmlDirectiveNode directiveNode, TypeReferenceBindingParserNode typeReferenceBindingParserNode, IdentifierNameBindingParserNode attributePropertyNameReference, - LiteralExpressionBindingParserNode initializer, + BindingParserNode initializer, ImmutableList imports) { DirectiveNode = directiveNode; diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs index ff47d82790..d8d50120f2 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs @@ -114,7 +114,7 @@ public IAbstractDirectiveAttributeReference BuildPropertyDeclarationAttributeRef DothtmlDirectiveNode directiveNode, IdentifierNameBindingParserNode propertyNameSyntax, TypeReferenceBindingParserNode typeSyntax, - LiteralExpressionBindingParserNode initializer, + BindingParserNode initializer, ImmutableList imports) { return new ResolvedPropertyDirectiveAttributeReference(directiveService, directiveNode, typeSyntax, propertyNameSyntax, initializer, imports); diff --git a/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs index 302a1c472f..2d554c2950 100644 --- a/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs +++ b/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs @@ -77,11 +77,11 @@ private AttributeInfo GetAttributeInfo(BindingParserNode attributeReference, Dot return new AttributeInfo( typeRef, new SimpleNameBindingParserNode("") { StartPosition = attributeReference.EndPosition }, - new LiteralExpressionBindingParserNode("") { StartPosition = attributeReference.EndPosition }); + new SimpleNameBindingParserNode("") { StartPosition = attributeReference.EndPosition }); } var attributePropertyReference = assignment.FirstExpression as MemberAccessBindingParserNode; - var initializer = assignment.SecondExpression as LiteralExpressionBindingParserNode; + var initializer = assignment.SecondExpression; var attributeTypeReference = attributePropertyReference?.TargetExpression; var attributePropertyNameReference = attributePropertyReference?.MemberNameExpression; @@ -93,10 +93,9 @@ private AttributeInfo GetAttributeInfo(BindingParserNode attributeReference, Dot attributeTypeReference = attributeTypeReference ?? new SimpleNameBindingParserNode(""); attributePropertyNameReference = attributePropertyNameReference ?? new SimpleNameBindingParserNode("") { StartPosition = attributeTypeReference.EndPosition }; } - if (initializer == null) + if (assignment.SecondExpression is not LiteralExpressionBindingParserNode) { directiveNode.AddError($"Value for property {attributeTypeReference.ToDisplayString()} of attribute {attributePropertyNameReference.ToDisplayString()} is missing or not a constant."); - initializer = new LiteralExpressionBindingParserNode("") { StartPosition = attributePropertyNameReference.EndPosition }; } var type = new ActualTypeReferenceBindingParserNode(attributeTypeReference); @@ -120,7 +119,7 @@ protected override ImmutableList CreateArtefact(IReadOnlyList n == property.InitializerSyntax)); - Assert.AreEqual(2,nodes.Count()); + Assert.AreEqual(2, property.Attributes.Count); + AssertEx.AssertNode(property.Attributes[0].Initializer, "True", 66, 5); + AssertEx.AssertNode(property.Attributes[1].Initializer, "False", 110, 6); } } } From 07df42b7ad052ba65ef9488bdb6553059e2fceef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Miku=C5=A1?= Date: Sat, 6 Jan 2024 11:37:18 +0100 Subject: [PATCH 3/6] Default initializer fixed for attributes --- .../Resolved/ResolvedPropertyDeclarationDirective.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs index 2bb7cf3ff7..7223d794b1 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedPropertyDeclarationDirective.cs @@ -62,7 +62,7 @@ private IEnumerable InstantiateAttributes(DothtmlDirectiveNode dothtmlDi (name, attributes) => { var attributeType = (attributes.First().Type as ResolvedTypeDescriptor)?.Type; - var properties = attributes.Select(a => (name: a.NameSyntax.Name, value: a.Initializer)); + var properties = attributes.Select(a => (name: a.NameSyntax.Name, value: (a.Initializer as LiteralExpressionBindingParserNode)?.Value ?? "")); return (attributeType, properties); }).ToList(); From e2503b5aced6792e3dc0082e0ffe03b62b12b2da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Miku=C5=A1?= Date: Sat, 6 Jan 2024 19:03:33 +0100 Subject: [PATCH 4/6] Split one test onto two as originally intended --- .../PropertyDirectiveTests.cs | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs index 0152003027..21fc5d24f1 100644 --- a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs +++ b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs @@ -50,7 +50,7 @@ @viewModel object } [TestMethod] - public void ResolvedTree_PropertyDirectiveArrayInicializer_ResolvedCorrectly() + public void ResolvedTree_PropertyDirectiveInvalidArrayInicializer_ResolvedCorrectly() { var root = ParseSource(@$" @viewModel object @@ -58,15 +58,28 @@ @viewModel object var property = root.Directives["property"].SingleOrDefault() as IAbstractPropertyDeclarationDirective; - Assert.AreEqual("System.String[]", property.PropertyType.FullName); - Assert.AreEqual("MyProperty", property.NameSyntax.Name); + Assert.AreEqual("System.String", property.PropertyType.FullName); + Assert.AreEqual("a", property.NameSyntax.Name); var nodes = property.InitializerSyntax.EnumerateChildNodes(); Assert.IsFalse(nodes.Any(n => n == property.InitializerSyntax)); + } + + [TestMethod] + public void ResolvedTree_PropertyDirectiveArrayInicializerAndAttributes_ResolvedCorrectly() + { + var root = ParseSource(@$" +@viewModel object +@property string[] MyProperty=["""",""""], MarkupOptionsAttribute.Required = true, MarkupOptionsAttribute.AllowBinding = false"); + + var property = root.Directives["property"].SingleOrDefault() as IAbstractPropertyDeclarationDirective; + + Assert.AreEqual("System.String[]", property.PropertyType.FullName); + Assert.AreEqual("MyProperty", property.NameSyntax.Name); Assert.AreEqual(2, property.Attributes.Count); - AssertEx.AssertNode(property.Attributes[0].Initializer, "True", 66, 5); - AssertEx.AssertNode(property.Attributes[1].Initializer, "False", 110, 6); + AssertEx.AssertNode(property.Attributes[0].Initializer, "True", 62, 5); + AssertEx.AssertNode(property.Attributes[1].Initializer, "False", 106, 6); } } } From 1c9a0815b6b8773150cbe79e8999bc92481100a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Miku=C5=A1?= Date: Mon, 8 Jan 2024 16:04:03 +0100 Subject: [PATCH 5/6] `AssertEx.AssertNode` renamed --- src/Tests/AssertEx.cs | 2 +- src/Tests/Parser/Binding/BindingParserTests.cs | 2 +- .../PropertyDirectiveTests.cs | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Tests/AssertEx.cs b/src/Tests/AssertEx.cs index bb56518dd6..e95d80d113 100644 --- a/src/Tests/AssertEx.cs +++ b/src/Tests/AssertEx.cs @@ -10,7 +10,7 @@ namespace DotVVM.Framework.Tests { public static class AssertEx { - public static void AssertNode(BindingParserNode node, string expectedDisplayString, int start, int length, bool hasErrors = false) + public static void BindingNode(BindingParserNode node, string expectedDisplayString, int start, int length, bool hasErrors = false) { Assert.AreEqual(expectedDisplayString, node.ToDisplayString(), $"Node {node.GetType().Name}: display string incorrect."); Assert.AreEqual(start, node.StartPosition, $"Node {node.GetType().Name}: Start position incorrect."); diff --git a/src/Tests/Parser/Binding/BindingParserTests.cs b/src/Tests/Parser/Binding/BindingParserTests.cs index 52f7a12386..75be814a8a 100644 --- a/src/Tests/Parser/Binding/BindingParserTests.cs +++ b/src/Tests/Parser/Binding/BindingParserTests.cs @@ -1490,7 +1490,7 @@ public void BindingParser_GenericMethodCall_MultipleGenericArguments() } private static void AssertNode(BindingParserNode node, string expectedDisplayString, int start, int length, bool hasErrors = false) - => AssertEx.AssertNode(node, expectedDisplayString, start, length, hasErrors); + => AssertEx.BindingNode(node, expectedDisplayString, start, length, hasErrors); private static string SkipWhitespaces(string str) => string.Join("", str.Where(c => !char.IsWhiteSpace(c))); diff --git a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs index 21fc5d24f1..98d5226d33 100644 --- a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs +++ b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs @@ -23,10 +23,10 @@ @viewModel object Assert.AreEqual(4, property.Attributes.Count); - AssertEx.AssertNode(property.Attributes[0].Initializer, "", 19, 0); - AssertEx.AssertNode(property.Attributes[1].Initializer, "", 28, 0); - AssertEx.AssertNode(property.Attributes[2].Initializer, "", 42, 1, true); - AssertEx.AssertNode(property.Attributes[3].Initializer, "t", 104, 2); + AssertEx.BindingNode(property.Attributes[0].Initializer, "", 19, 0); + AssertEx.BindingNode(property.Attributes[1].Initializer, "", 28, 0); + AssertEx.BindingNode(property.Attributes[2].Initializer, "", 42, 1, true); + AssertEx.BindingNode(property.Attributes[3].Initializer, "t", 104, 2); } [TestMethod] @@ -78,8 +78,8 @@ @viewModel object Assert.AreEqual("MyProperty", property.NameSyntax.Name); Assert.AreEqual(2, property.Attributes.Count); - AssertEx.AssertNode(property.Attributes[0].Initializer, "True", 62, 5); - AssertEx.AssertNode(property.Attributes[1].Initializer, "False", 106, 6); + AssertEx.BindingNode(property.Attributes[0].Initializer, "True", 62, 5); + AssertEx.BindingNode(property.Attributes[1].Initializer, "False", 106, 6); } } } From 99be07b10017791a5242ff91f599c4c3ea11809d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milan=20Miku=C5=A1?= Date: Mon, 8 Jan 2024 16:45:45 +0100 Subject: [PATCH 6/6] Raw literal used --- .../DefaultControlTreeResolver/PropertyDirectiveTests.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs index 98d5226d33..1128d1f66e 100644 --- a/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs +++ b/src/Tests/Runtime/ControlTree/DefaultControlTreeResolver/PropertyDirectiveTests.cs @@ -68,9 +68,10 @@ @viewModel object [TestMethod] public void ResolvedTree_PropertyDirectiveArrayInicializerAndAttributes_ResolvedCorrectly() { - var root = ParseSource(@$" + var root = ParseSource(""" @viewModel object -@property string[] MyProperty=["""",""""], MarkupOptionsAttribute.Required = true, MarkupOptionsAttribute.AllowBinding = false"); +@property string[] MyProperty=["",""], MarkupOptionsAttribute.Required = true, MarkupOptionsAttribute.AllowBinding = false +"""); var property = root.Directives["property"].SingleOrDefault() as IAbstractPropertyDeclarationDirective;