diff --git a/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs b/src/Framework/Framework/Compilation/ControlTree/IAbstractTreeBuilder.cs index af6509e636..32f2edaac7 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, ActualTypeReferenceBindingParserNode typeSyntax, LiteralExpressionBindingParserNode initializer, ImmutableList imports); + IAbstractDirectiveAttributeReference BuildPropertyDeclarationAttributeReference(DothtmlDirectiveNode directiveNode, IdentifierNameBindingParserNode propertyNameSyntax, TypeReferenceBindingParserNode typeSyntax, LiteralExpressionBindingParserNode 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/ResolvedTreeBuilder.cs b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs index e0ea0beadf..ff47d82790 100644 --- a/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs +++ b/src/Framework/Framework/Compilation/ControlTree/Resolved/ResolvedTreeBuilder.cs @@ -113,7 +113,7 @@ public IAbstractPropertyDeclarationDirective BuildPropertyDeclarationDirective( public IAbstractDirectiveAttributeReference BuildPropertyDeclarationAttributeReference( DothtmlDirectiveNode directiveNode, IdentifierNameBindingParserNode propertyNameSyntax, - ActualTypeReferenceBindingParserNode typeSyntax, + TypeReferenceBindingParserNode typeSyntax, LiteralExpressionBindingParserNode initializer, ImmutableList imports) { diff --git a/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs b/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs index 86bcbbd304..90910a6dc0 100644 --- a/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs +++ b/src/Framework/Framework/Compilation/Directives/PropertyDeclarationDirectiveCompiler.cs @@ -59,9 +59,9 @@ protected override IAbstractPropertyDeclarationDirective Resolve(DothtmlDirectiv return TreeBuilder.BuildPropertyDeclarationDirective(directiveNode, type, name, declaration?.Initializer, resolvedAttributes, valueSyntaxRoot, imports); } - private List<(ActualTypeReferenceBindingParserNode type, IdentifierNameBindingParserNode name, LiteralExpressionBindingParserNode initializer)> ProcessPropertyDirectiveAttributeReference(DothtmlDirectiveNode directiveNode, List attributeReferences) + private List<(TypeReferenceBindingParserNode type, IdentifierNameBindingParserNode name, LiteralExpressionBindingParserNode initializer)> ProcessPropertyDirectiveAttributeReference(DothtmlDirectiveNode directiveNode, List attributeReferences) { - var result = new List<(ActualTypeReferenceBindingParserNode, IdentifierNameBindingParserNode, LiteralExpressionBindingParserNode)>(); + var result = new List<(TypeReferenceBindingParserNode, IdentifierNameBindingParserNode, LiteralExpressionBindingParserNode)>(); foreach (var attributeReference in attributeReferences) { if (attributeReference is not BinaryOperatorBindingParserNode { Operator: BindingTokenType.AssignOperator } assignment) @@ -75,7 +75,7 @@ protected override IAbstractPropertyDeclarationDirective Resolve(DothtmlDirectiv var attributePropertyNameReference = attributePropertyReference?.MemberNameExpression; var initializer = assignment.SecondExpression as LiteralExpressionBindingParserNode; - if (attributeTypeReference == null || attributePropertyNameReference == null) + if (attributeTypeReference is null || attributePropertyNameReference is null) { directiveNode.AddError("Property attributes must be in the form Attribute.Property = value."); continue; @@ -85,7 +85,10 @@ protected override IAbstractPropertyDeclarationDirective Resolve(DothtmlDirectiv directiveNode.AddError($"Value for property {attributeTypeReference.ToDisplayString()} of attribute {attributePropertyNameReference.ToDisplayString()} is missing or not a constant."); continue; } - result.Add((new ActualTypeReferenceBindingParserNode(attributeTypeReference), attributePropertyNameReference, initializer)); + + var type = new ActualTypeReferenceBindingParserNode(attributeTypeReference); + type.TransferTokens(attributeTypeReference); + result.Add(new (type, attributePropertyNameReference, initializer)); } return result; } diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs index 49173b122c..566206a076 100644 --- a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParser.cs @@ -585,14 +585,14 @@ private bool TryReadTypeReference([NotNullWhen(returnValue: true)] out TypeRefer // Generic if (!TryReadGenericArguments(startIndex, expression, out var typeOrFunction)) return false; - expression = typeOrFunction!.ToTypeReference(); + expression = CreateNode(typeOrFunction!.ToTypeReference(), startIndex); } else if (next.Type == BindingTokenType.QuestionMarkOperator) { // Nullable Read(); - var typeExpr = expression as TypeReferenceBindingParserNode ?? new ActualTypeReferenceBindingParserNode(expression); - expression = CreateNode(new NullableTypeReferenceBindingParserNode(typeExpr), startIndex); + + expression = CreateNode(new NullableTypeReferenceBindingParserNode(EnsureTypeExpression(expression)), startIndex); } else if (next.Type == BindingTokenType.OpenArrayBrace) { @@ -602,8 +602,8 @@ private bool TryReadTypeReference([NotNullWhen(returnValue: true)] out TypeRefer if (next?.Type != BindingTokenType.CloseArrayBrace) return false; Read(); - var typeExpr = expression as TypeReferenceBindingParserNode ?? new ActualTypeReferenceBindingParserNode(expression); - expression = CreateNode(new ArrayTypeReferenceBindingParserNode(typeExpr), startIndex); + + expression = CreateNode(new ArrayTypeReferenceBindingParserNode(EnsureTypeExpression(expression)), startIndex); } else { @@ -612,10 +612,20 @@ private bool TryReadTypeReference([NotNullWhen(returnValue: true)] out TypeRefer next = Peek(); } - typeNode = expression as TypeReferenceBindingParserNode ?? new ActualTypeReferenceBindingParserNode(expression); + typeNode = EnsureTypeExpression(expression); return true; } + private static TypeReferenceBindingParserNode EnsureTypeExpression(BindingParserNode expression) + { + if (expression is TypeReferenceBindingParserNode typeExpr) { return typeExpr; } + + typeExpr = new ActualTypeReferenceBindingParserNode(expression); + typeExpr.TransferTokens(expression); + + return typeExpr; + } + private BindingParserNode ReadIdentifierExpression(bool onlyTypeName) { var startIndex = CurrentIndex; @@ -632,7 +642,7 @@ private BindingParserNode ReadIdentifierExpression(bool onlyTypeName) Read(); var member = ReadIdentifierNameExpression(); if (expression is TypeOrFunctionReferenceBindingParserNode typeOrFunction) - expression = typeOrFunction.ToTypeReference(); + expression = CreateNode(typeOrFunction.ToTypeReference(), startIndex); expression = CreateNode(new MemberAccessBindingParserNode(expression, member), startIndex); } @@ -647,7 +657,7 @@ private BindingParserNode ReadIdentifierExpression(bool onlyTypeName) else if (!onlyTypeName && next.Type == BindingTokenType.OpenParenthesis) { if (expression is TypeOrFunctionReferenceBindingParserNode typeOrFunction) - expression = typeOrFunction.ToFunctionReference(); + expression = CreateNode(typeOrFunction.ToFunctionReference(), startIndex); expression = ReadFunctionCall(startIndex, expression); } diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParserNode.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParserNode.cs index ea3ac675b2..34a9d487b2 100644 --- a/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParserNode.cs +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/BindingParserNode.cs @@ -47,5 +47,24 @@ public virtual IEnumerable EnumerateNodes() public abstract string ToDisplayString(); + internal void TransferTokens(BindingParserNode sourceNode, int? startPosition = null) + { + + StartPosition = startPosition ?? sourceNode.StartPosition; + Length = 0; + + Tokens.Clear(); + Tokens.Capacity = sourceNode.Tokens.Capacity; + + for (int i = 0; i < sourceNode.Tokens.Count; i++) + { + var token = sourceNode.Tokens[i]; + if ((startPosition ?? 0) <= token.StartPosition) + { + Length += token.Length; + Tokens.Add(token); + } + } + } } } diff --git a/src/Framework/Framework/Compilation/Parser/Binding/Parser/TypeOrFunctionReferenceBindingParserNode.cs b/src/Framework/Framework/Compilation/Parser/Binding/Parser/TypeOrFunctionReferenceBindingParserNode.cs index c32653a625..abd790dc99 100644 --- a/src/Framework/Framework/Compilation/Parser/Binding/Parser/TypeOrFunctionReferenceBindingParserNode.cs +++ b/src/Framework/Framework/Compilation/Parser/Binding/Parser/TypeOrFunctionReferenceBindingParserNode.cs @@ -32,6 +32,8 @@ public override string ToDisplayString() public TypeReferenceBindingParserNode ToTypeReference() { var type = new ActualTypeReferenceBindingParserNode(TypeOrFunction); + type.TransferTokens(TypeOrFunction); + if (TypeArguments.Count == 0) return type; @@ -52,7 +54,10 @@ public BindingParserNode ToFunctionReference() if (TypeArguments.Count == 0) return memberAccess; - var genericName = new GenericNameBindingParserNode(memberAccess.MemberNameExpression.NameToken, TypeArguments); + var name = memberAccess.MemberNameExpression.NameToken; + var genericName = new GenericNameBindingParserNode(name, TypeArguments); + genericName.TransferTokens(this, name.StartPosition); + memberAccess.MemberNameExpression = genericName; return memberAccess; } diff --git a/src/Tests/Parser/Binding/BindingParserTests.cs b/src/Tests/Parser/Binding/BindingParserTests.cs index 04e81e9efa..56dca98662 100644 --- a/src/Tests/Parser/Binding/BindingParserTests.cs +++ b/src/Tests/Parser/Binding/BindingParserTests.cs @@ -882,7 +882,7 @@ public void BindingParser_AssemblyQualifiedName_InvalidAssemblyName(string bindi [TestMethod] [DataRow("(arg) => Method(arg)", DisplayName = "Simple implicit single-parameter lambda expression with parentheses.")] - [DataRow("arg => Method(arg)", DisplayName = "Simple implicit single-parameter lambda expression without parentheses.")] + [DataRow("arg => Method(arg)", DisplayName = "Simple implicit single-parameter lambda expression without parentheses.")] [DataRow(" arg => Method ( arg )", DisplayName = "Simple lambda with various whitespaces.")] [DataRow("arg =>Method(arg)", DisplayName = "Simple lambda with various whitespaces.")] [DataRow("arg=>Method(arg)", DisplayName = "Simple lambda with various whitespaces.")] @@ -955,7 +955,7 @@ public void BindingParser_Lambda_NoTypeInfo_MultipleParameters() [DataRow("(string arg) => Method(arg)", "string")] [DataRow("(float arg) => Method(arg)", "float")] [DataRow("(decimal arg) => Method(arg)", "decimal")] - [DataRow("(System.Collections.Generic.List arg) => Method(arg)", "System.Collections.Generic.List")] + [DataRow("(System.Collections.Generic.List.Subtype arg) => Method(arg)", "System.Collections.Generic.List.Subtype")] public void BindingParser_Lambda_WithTypeInfo_SingleParameter(string expr, string type) { var parser = bindingParserNodeFactory.SetupParser(expr); @@ -966,9 +966,10 @@ public void BindingParser_Lambda_WithTypeInfo_SingleParameter(string expr, strin var parameters = lambda.ParameterExpressions; Assert.AreEqual(1, parameters.Count); - Assert.AreEqual(type, parameters[0].Type.ToDisplayString()); - Assert.AreEqual("arg", parameters[0].Name.ToDisplayString()); - Assert.AreEqual("Method(arg)", body.ToDisplayString()); + + AssertNode(parameters[0].Type, type, 1, type.Length+1); + AssertNode(parameters[0].Name, "arg", type.Length + 2, 3); + AssertNode(body, "Method(arg)", type.Length + 10, 11); } [TestMethod] @@ -1158,7 +1159,7 @@ public void BindingParser_MultiblockExpression_EmptyBlockFourBlocks_Invalid(stri Assert.AreEqual(SkipWhitespaces(bindingExpression), SkipWhitespaces(node.ToDisplayString())); } - + [TestMethod] public void BindingParser_VariableExpression_3Vars() { @@ -1191,9 +1192,66 @@ public void BindingParser_MinimalPropertyDeclaration() var type = root.PropertyType.CastTo(); var name = root.Name.CastTo(); - Assert.AreEqual("System.String MyProperty", root.ToDisplayString()); - Assert.AreEqual("System.String", type.ToDisplayString()); - Assert.AreEqual("MyProperty", name.ToDisplayString()); + AssertNode(root, "System.String MyProperty", 0, 24); + AssertNode(type, "System.String", 0, 14); + AssertNode(name, "MyProperty", 14, 10); + } + + [TestMethod] + public void BindingParser_NullablePropertyDeclaration() + { + var parser = bindingParserNodeFactory.SetupParser("System.String? MyProperty"); + var declaration = parser.ReadPropertyDirectiveValue(); + + var root = declaration.CastTo(); + var nullable = root.PropertyType.CastTo(); + var type = nullable.InnerType.CastTo(); + var name = root.Name.CastTo(); + + AssertNode(root, "System.String? MyProperty", 0, 25); + AssertNode(nullable, "System.String?", 0, 14); + AssertNode(type, "System.String", 0, 13); + AssertNode(name, "MyProperty", 14, 11); + } + + [TestMethod] + public void BindingParser_ComplexTypePropertyDeclaration() + { + var parser = bindingParserNodeFactory.SetupParser("System.Func? MyProperty"); + var declaration = parser.ReadPropertyDirectiveValue(); + + var root = declaration.CastTo(); + var nullableFunc = root.PropertyType.CastTo(); + var funcGeneric = nullableFunc.InnerType.CastTo(); + var func = funcGeneric.Type.CastTo(); + + var arg1Nullable = funcGeneric.Arguments[0].CastTo(); + var arg2Nullable = funcGeneric.Arguments[1].CastTo(); + var arg4Array = funcGeneric.Arguments[3].CastTo(); + + var arg1 = arg1Nullable.InnerType.CastTo(); + var arg2 = arg2Nullable.InnerType.CastTo(); + var arg3 = funcGeneric.Arguments[2].CastTo(); + var arg4Nullable = arg4Array.ElementType.CastTo(); + + var arg4 = arg4Nullable.InnerType.CastTo(); + + AssertNode(root, "System.Func? MyProperty", 0, 89); + AssertNode(nullableFunc, "System.Func?", 0, 78); + AssertNode(funcGeneric, "System.Func", 0, 77); + AssertNode(func, "System.Func", 0, 11); + AssertNode(root.Name, "MyProperty", 78, 11); + + AssertNode(arg1Nullable, "System.String?", 12, 14); + AssertNode(arg2Nullable, "int?", 28, 4); + AssertNode(arg3, "string", 34, 6); + AssertNode(arg4Array, "IdentifierNameBindingParserNode?[]", 42, 34); + + AssertNode(arg1, "System.String", 12, 13); + AssertNode(arg2, "int", 28, 3); + AssertNode(arg4Nullable, "IdentifierNameBindingParserNode?", 42, 32); + + AssertNode(arg4, "IdentifierNameBindingParserNode", 42, 31); } [TestMethod] @@ -1207,10 +1265,10 @@ public void BindingParser_InitializedPropertyDeclaration() var name = root.Name.CastTo(); var init = root.Initializer.CastTo(); - Assert.AreEqual("System.String MyProperty = \"Test\"", root.ToDisplayString()); - Assert.AreEqual("System.String", type.ToDisplayString()); - Assert.AreEqual("MyProperty", name.ToDisplayString()); - Assert.AreEqual("\"Test\"", init.ToDisplayString()); + AssertNode(root, "System.String MyProperty = \"Test\"", 0, 33); + AssertNode(type, "System.String", 0, 14); + AssertNode(name, "MyProperty", 14, 11); + AssertNode(init, "\"Test\"", 27, 6); } [TestMethod] @@ -1229,12 +1287,12 @@ public void BindingParser_InitializedAttributedPropertyDeclaration() var att1 = root.Attributes[0].CastTo(); var att2 = root.Attributes[1].CastTo(); - Assert.AreEqual("System.String MyProperty = \"Test\", MarkupOptions.AllowHardCodedValue = False, MarkupOptions.Required = True", root.ToDisplayString()); - Assert.AreEqual("System.String", type.ToDisplayString()); - Assert.AreEqual("MyProperty", name.ToDisplayString()); - Assert.AreEqual("\"Test\"", init.ToDisplayString()); - Assert.AreEqual("MarkupOptions.AllowHardCodedValue = False", att1.ToDisplayString()); - Assert.AreEqual("MarkupOptions.Required = True", att2.ToDisplayString()); + AssertNode(root, "System.String MyProperty = \"Test\", MarkupOptions.AllowHardCodedValue = False, MarkupOptions.Required = True", 0, 107); + AssertNode(type, "System.String", 0, 14); + AssertNode(name, "MyProperty", 14, 11); + AssertNode(init, "\"Test\"", 27, 6); + AssertNode(att1, "MarkupOptions.AllowHardCodedValue = False", 35, 41); + AssertNode(att2, "MarkupOptions.Required = True", 77, 30); } [TestMethod] @@ -1244,7 +1302,7 @@ public void BindingParser_AttributedPropertyDeclaration() var declaration = parser.ReadPropertyDirectiveValue(); var root = declaration.CastTo(); - var type = root.PropertyType.CastTo(); + var type = root.PropertyType.CastTo(); var name = root.Name.CastTo(); var attributes = root.Attributes; @@ -1254,11 +1312,11 @@ public void BindingParser_AttributedPropertyDeclaration() var att1 = root.Attributes[0].CastTo(); var att2 = root.Attributes[1].CastTo(); - Assert.AreEqual("System.String MyProperty, MarkupOptions.AllowHardCodedValue = False, MarkupOptions.Required = True", root.ToDisplayString()); - Assert.AreEqual("System.String", type.ToDisplayString()); - Assert.AreEqual("MyProperty", name.ToDisplayString()); - Assert.AreEqual("MarkupOptions.AllowHardCodedValue = False", att1.ToDisplayString()); - Assert.AreEqual("MarkupOptions.Required = True", att2.ToDisplayString()); + AssertNode(root, "System.String MyProperty, MarkupOptions.AllowHardCodedValue = False, MarkupOptions.Required = True", 0, 98); + AssertNode(type, "System.String", 0, 14); + AssertNode(name, "MyProperty", 14, 10); + AssertNode(att1, "MarkupOptions.AllowHardCodedValue = False", 26, 41); + AssertNode(att2, "MarkupOptions.Required = True", 68, 30); } [TestMethod] @@ -1268,7 +1326,8 @@ public void BindingParser_AttributedArrayInitializedPropertyDeclaration() var declaration = parser.ReadPropertyDirectiveValue(); var root = declaration.CastTo(); - var type = root.PropertyType.CastTo(); + var type = root.PropertyType.CastTo(); + var elementType = type.ElementType.CastTo(); var name = root.Name.CastTo(); var attributes = root.Attributes; @@ -1282,16 +1341,104 @@ public void BindingParser_AttributedArrayInitializedPropertyDeclaration() var element2Initializer = root.Initializer.CastTo().ElementInitializers[1].CastTo(); var element3Initializer = root.Initializer.CastTo().ElementInitializers[2].CastTo(); - Assert.AreEqual("Namespace.Enum[] MyProperty = [ Namespace.Enum.Value1, Namespace.Enum.Value2, Namespace.Enum.Value3 ], MarkupOptions.AllowHardCodedValue = False, MarkupOptions.Required = True", root.ToDisplayString()); - Assert.AreEqual("Namespace.Enum[]", type.ToDisplayString()); - Assert.AreEqual("MyProperty", name.ToDisplayString()); + AssertNode(root, "Namespace.Enum[] MyProperty = [ Namespace.Enum.Value1, Namespace.Enum.Value2, Namespace.Enum.Value3 ], MarkupOptions.AllowHardCodedValue = False, MarkupOptions.Required = True", 0, 175); + AssertNode(type, "Namespace.Enum[]", 0, 16); + AssertNode(elementType, "Namespace.Enum", 0, 14); + AssertNode(name, "MyProperty", 16, 12); - Assert.AreEqual("Namespace.Enum.Value1", element1Initializer.ToDisplayString()); - Assert.AreEqual("Namespace.Enum.Value2", element2Initializer.ToDisplayString()); - Assert.AreEqual("Namespace.Enum.Value3", element3Initializer.ToDisplayString()); + AssertNode(element1Initializer, "Namespace.Enum.Value1", 32, 21); + AssertNode(element2Initializer, "Namespace.Enum.Value2", 55, 21); + AssertNode(element3Initializer, "Namespace.Enum.Value3", 78, 22); - Assert.AreEqual("MarkupOptions.AllowHardCodedValue = False", att1.ToDisplayString()); - Assert.AreEqual("MarkupOptions.Required = True", att2.ToDisplayString()); + AssertNode(att1, "MarkupOptions.AllowHardCodedValue = False", 103, 41); + AssertNode(att2, "MarkupOptions.Required = True", 145, 30); + } + + [TestMethod] + public void BindingParser_GenericTypePropertyDeclaration() + { + var parser = bindingParserNodeFactory.SetupParser("IDictionary MyProperty"); + var declaration = parser.ReadPropertyDirectiveValue(); + + var root = declaration.CastTo(); + var genericType = root.PropertyType.CastTo(); + var name = root.Name.CastTo(); + + var type = genericType.Type.CastTo(); + var arg1 = genericType.Arguments[0].CastTo(); + var arg2 = genericType.Arguments[1].CastTo(); + + AssertNode(root, "IDictionary MyProperty", 0, 42); + AssertNode(name, "MyProperty", 31, 11); + AssertNode(genericType, "IDictionary", 0, 31); + AssertNode(type, "IDictionary", 0, 11); + AssertNode(arg1, "int", 12, 3); + AssertNode(arg2, "System.String", 17, 13); + } + + [TestMethod] + public void BindingParser_GenericMethodCall_SimpleName() + { + var source = "GetType(StringProp)"; + var parser = bindingParserNodeFactory.SetupParser(source); + var root = parser.ReadExpression().As(); + + var generic = root.TargetExpression.As(); + var typeArgument = generic.TypeArguments[0].As(); + + AssertNode(root, source, 0, source.Length); + AssertNode(generic, "GetType", 0, source.Length-12); + AssertNode(typeArgument, "string", 8, 6); + } + + [TestMethod] + public void BindingParser_GenericMethodCall_MemberAccessName() + { + var source = "service.GetType(StringProp)"; + + var parser = bindingParserNodeFactory.SetupParser(source); + var root = parser.ReadExpression().As(); + + var memberAccess = root.TargetExpression.As(); + var generic = memberAccess.MemberNameExpression.As(); + var typeArgument = generic.TypeArguments[0].As(); + + AssertNode(root, source, 0, source.Length); + AssertNode(memberAccess, "service.GetType", 0, source.Length-12); + AssertNode(generic, "GetType", 8, source.Length - 20); + + AssertNode(typeArgument, "string?", 16, 7); + + } + + [TestMethod] + public void BindingParser_GenericMethodCall_MultipleGenericArguments() + { + var source = "_this.Modal.GetType(StringProp)"; + + var parser = bindingParserNodeFactory.SetupParser(source); + var root = parser.ReadExpression().As(); + + var memberAccess1 = root.TargetExpression.As(); + var memberAccess2 = memberAccess1.TargetExpression.As(); + var generic = memberAccess1.MemberNameExpression.As(); + var typeArgument1 = generic.TypeArguments[0].As(); + var typeArgument2 = generic.TypeArguments[1].As(); + + AssertNode(root, source, 0, source.Length); + AssertNode(memberAccess1, "_this.Modal.GetType", 0, source.Length - 12); + AssertNode(memberAccess2, "_this.Modal", 0, 11); + AssertNode(generic, "GetType", 12, 31); + + AssertNode(typeArgument1, "string?", 20, 7); + AssertNode(typeArgument2, "System.String", 29, 13); + } + + private static void AssertNode(BindingParserNode elementType, string expectedDisplayString, int start, int length) + { + Assert.AreEqual(expectedDisplayString, elementType.ToDisplayString(), $"Node {elementType.GetType().Name}: display string incorrect."); + Assert.AreEqual(start, elementType.StartPosition, $"Node {elementType.GetType().Name}: Start position incorrect."); + Assert.AreEqual(length, elementType.Length, $"Node {elementType.GetType().Name}: Length incorrect."); } private static string SkipWhitespaces(string str) => string.Join("", str.Where(c => !char.IsWhiteSpace(c)));