public void WriteTagHelperProperty_Runtime_NonStringIndexer_RendersCorrectly()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension();
            var context   = TestCodeRenderingContext.CreateRuntime();

            var tagHelperNode = new TagHelperIntermediateNode();
            var node          = new DefaultTagHelperPropertyIntermediateNode()
            {
                AttributeName      = "foo-bound",
                AttributeStructure = AttributeStructure.DoubleQuotes,
                BoundAttribute     = IntIndexerTagHelper.BoundAttributes.Single(),
                FieldName          = "__InputTagHelper",
                IsIndexerNameMatch = true,
                PropertyName       = "IntIndexer",
                TagHelper          = IntIndexerTagHelper,
                Source             = Span,
                Children           =
                {
                    new CSharpExpressionIntermediateNode()
                    {
                        Children ={ new IntermediateToken                {
                          Kind = TokenKind.CSharp, Content = "32",
                      } },
                    }
                }
            };

            tagHelperNode.Children.Add(node);
            Push(context, tagHelperNode);

            // Act
            extension.WriteTagHelperProperty(context, node);

            // Assert
            var csharp = context.CodeWriter.GenerateCode();

            Assert.Equal(
                @"if (__InputTagHelper.IntIndexer == null)
{
    throw new InvalidOperationException(InvalidTagHelperIndexerAssignment(""foo-bound"", ""InputTagHelper"", ""IntIndexer""));
}
#nullable restore
#line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32;

#line default
#line hidden
#nullable disable
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
                csharp,
                ignoreLineEndingDifferences: true);
        }
示例#2
0
            public void VisitExtension(DefaultTagHelperPropertyIntermediateNode node)
            {
                if (!(node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty)) ||
                    node.Children.Count != 1 ||
                    !(node.Children.First() is HtmlContentIntermediateNode))
                {
                    return;
                }

                var htmlContentNode = node.Children.First() as HtmlContentIntermediateNode;
                var plainTextValue  = GetContent(htmlContentNode);

                PreallocatedTagHelperPropertyValueIntermediateNode declaration = null;

                for (var i = 0; i < _classDeclaration.Children.Count; i++)
                {
                    var current = _classDeclaration.Children[i];

                    if (current is PreallocatedTagHelperPropertyValueIntermediateNode existingDeclaration)
                    {
                        if (string.Equals(existingDeclaration.AttributeName, node.AttributeName, StringComparison.Ordinal) &&
                            string.Equals(existingDeclaration.Value, plainTextValue, StringComparison.Ordinal) &&
                            existingDeclaration.AttributeStructure == node.AttributeStructure)
                        {
                            declaration = existingDeclaration;
                            break;
                        }
                    }
                }

                if (declaration == null)
                {
                    var variableCount = _classDeclaration.Children.Count - _variableCountOffset;
                    var preAllocatedAttributeVariableName = PreAllocatedAttributeVariablePrefix + variableCount;
                    declaration = new PreallocatedTagHelperPropertyValueIntermediateNode()
                    {
                        VariableName       = preAllocatedAttributeVariableName,
                        AttributeName      = node.AttributeName,
                        Value              = plainTextValue,
                        AttributeStructure = node.AttributeStructure,
                    };
                    _classDeclaration.Children.Insert(_preallocatedDeclarationCount++, declaration);
                }

                var setPreallocatedProperty = new PreallocatedTagHelperPropertyIntermediateNode(node)
                {
                    VariableName = declaration.VariableName,
                };

                var nodeIndex = Parent.Children.IndexOf(node);

                Parent.Children[nodeIndex] = setPreallocatedProperty;
            }
        public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension();
            var context   = TestCodeRenderingContext.CreateDesignTime();

            var tagHelperNode = new TagHelperIntermediateNode();
            var node          = new DefaultTagHelperPropertyIntermediateNode()
            {
                AttributeName      = "foo-bound",
                AttributeStructure = AttributeStructure.DoubleQuotes,
                BoundAttribute     = IntIndexerTagHelper.BoundAttributes.Single(),
                FieldName          = "__InputTagHelper",
                IsIndexerNameMatch = true,
                PropertyName       = "IntIndexer",
                TagHelper          = IntIndexerTagHelper,
                Source             = Span,
                Children           =
                {
                    new CSharpExpressionIntermediateNode()
                    {
                        Children ={ new IntermediateToken                {
                          Kind = TokenKind.CSharp, Content = "32",
                      } },
                    }
                }
            };

            tagHelperNode.Children.Add(node);
            Push(context, tagHelperNode);

            // Act
            extension.WriteTagHelperProperty(context, node);

            // Assert
            var csharp = context.CodeWriter.GenerateCode();

            Assert.Equal(
                @"
#nullable restore
#line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32;

#line default
#line hidden
#nullable disable
",
                csharp,
                ignoreLineEndingDifferences: true);
        }
示例#4
0
        public PreallocatedTagHelperPropertyIntermediateNode(DefaultTagHelperPropertyIntermediateNode propertyNode)
        {
            if (propertyNode == null)
            {
                throw new ArgumentNullException(nameof(propertyNode));
            }

            AttributeName      = propertyNode.AttributeName;
            AttributeStructure = propertyNode.AttributeStructure;
            BoundAttribute     = propertyNode.BoundAttribute;
            FieldName          = propertyNode.FieldName;
            IsIndexerNameMatch = propertyNode.IsIndexerNameMatch;
            PropertyName       = propertyNode.PropertyName;
            Source             = propertyNode.Source;
            TagHelper          = propertyNode.TagHelper;
        }
        public void WriteTagHelperProperty_Runtime_StringProperty_HtmlContent_RendersCorrectly()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension();
            var context   = TestCodeRenderingContext.CreateRuntime();

            var tagHelperNode = new TagHelperIntermediateNode();
            var node          = new DefaultTagHelperPropertyIntermediateNode()
            {
                AttributeName      = "bound",
                AttributeStructure = AttributeStructure.DoubleQuotes,
                BoundAttribute     = StringPropertyTagHelper.BoundAttributes.Single(),
                FieldName          = "__InputTagHelper",
                IsIndexerNameMatch = false,
                PropertyName       = "StringProp",
                TagHelper          = StringPropertyTagHelper,
                Children           =
                {
                    new HtmlContentIntermediateNode()
                    {
                        Children ={ new IntermediateToken                {
                          Kind = TokenKind.Html, Content = "\"value\"",
                      } },
                    }
                }
            };

            tagHelperNode.Children.Add(node);
            Push(context, tagHelperNode);

            // Act
            extension.WriteTagHelperProperty(context, node);

            // Assert
            var csharp = context.CodeWriter.GenerateCode();

            // The attribute value is not rendered inline because we are not using the preallocated writer.
            Assert.Equal(
                @"BeginWriteTagHelperAttribute();
Render Children
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
__InputTagHelper.StringProp = __tagHelperStringValueBuffer;
__tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.StringProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
                csharp,
                ignoreLineEndingDifferences: true);
        }
示例#6
0
        [Fact] // We don't actually assign the expression result at design time, we just use string.Empty as a placeholder.
        public void WriteTagHelperProperty_DesignTime_StringProperty_NonHtmlContent_RendersCorrectly()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension()
            {
                DesignTime = true
            };
            var context = TestCodeRenderingContext.CreateDesignTime();

            var tagHelperNode = new TagHelperIntermediateNode();
            var node          = new DefaultTagHelperPropertyIntermediateNode()
            {
                AttributeName      = "bound",
                AttributeStructure = AttributeStructure.DoubleQuotes,
                BoundAttribute     = StringPropertyTagHelper.BoundAttributes.Single(),
                FieldName          = "__InputTagHelper",
                IsIndexerNameMatch = false,
                PropertyName       = "StringProp",
                TagHelper          = StringPropertyTagHelper,
                Children           =
                {
                    new CSharpExpressionIntermediateNode()
                    {
                        Children ={ new IntermediateToken                {
                          Kind = TokenKind.CSharp, Content = "\"3+5\"",
                      } },
                    }
                }
            };

            tagHelperNode.Children.Add(node);
            Push(context, tagHelperNode);

            // Act
            extension.WriteTagHelperProperty(context, node);

            // Assert
            var csharp = context.CodeWriter.GenerateCode();

            Assert.Equal(
                @"Render Children
__InputTagHelper.StringProp = string.Empty;
",
                csharp,
                ignoreLineEndingDifferences: true);
        }
示例#7
0
        public void WriteTagHelperProperty_DesignTime_NonStringProperty_SecondUseOfAttribute()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension()
            {
                DesignTime = true
            };
            var context = TestCodeRenderingContext.CreateDesignTime();

            var tagHelperNode = new TagHelperIntermediateNode();
            var node1         = new DefaultTagHelperPropertyIntermediateNode()
            {
                // We only look at the attribute name here.
                AttributeName = "bound",
                FieldName     = "__OtherTagHelper",
                PropertyName  = "IntProp",
            };
            var node2 = new DefaultTagHelperPropertyIntermediateNode()
            {
                AttributeName      = "bound",
                AttributeStructure = AttributeStructure.DoubleQuotes,
                BoundAttribute     = IntPropertyTagHelper.BoundAttributes.Single(),
                FieldName          = "__InputTagHelper",
                IsIndexerNameMatch = false,
                PropertyName       = "IntProp",
                TagHelper          = IntPropertyTagHelper,
                Source             = Span,
            };

            tagHelperNode.Children.Add(node1);
            tagHelperNode.Children.Add(node2);
            Push(context, tagHelperNode);

            // Act
            extension.WriteTagHelperProperty(context, node2);

            // Assert
            var csharp = context.CodeWriter.GenerateCode();

            Assert.Equal(
                @"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
                csharp,
                ignoreLineEndingDifferences: true);
        }
        public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_WithoutLocation()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension();
            var context   = TestCodeRenderingContext.CreateRuntime();

            var tagHelperNode = new TagHelperIntermediateNode();
            var node          = new DefaultTagHelperPropertyIntermediateNode()
            {
                AttributeName      = "bound",
                AttributeStructure = AttributeStructure.DoubleQuotes,
                BoundAttribute     = IntPropertyTagHelper.BoundAttributes.Single(),
                FieldName          = "__InputTagHelper",
                IsIndexerNameMatch = false,
                PropertyName       = "IntProp",
                TagHelper          = IntPropertyTagHelper,
                Children           =
                {
                    new CSharpExpressionIntermediateNode()
                    {
                        Children ={ new IntermediateToken                {
                          Kind = TokenKind.CSharp, Content = "32",
                      } },
                    }
                }
            };

            tagHelperNode.Children.Add(node);
            Push(context, tagHelperNode);

            // Act
            extension.WriteTagHelperProperty(context, node);

            // Assert
            var csharp = context.CodeWriter.GenerateCode();

            Assert.Equal(
                @"__InputTagHelper.IntProp = 32;
__tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.IntProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
                csharp,
                ignoreLineEndingDifferences: true);
        }
        public void RenderTagHelperAttributeInline_NonStringIndexerMatch_TemplateInAttribute_Errors()
        {
            // Arrange
            var extension = new DefaultTagHelperTargetExtension();
            var context   = TestCodeRenderingContext.CreateRuntime();
            var node      = new DefaultTagHelperPropertyIntermediateNode()
            {
                BoundAttribute     = IntIndexerTagHelper.BoundAttributes.Single(),
                IsIndexerNameMatch = true,
            };
            var expectedLocation   = new SourceSpan(100, 10);
            var expectedDiagnostic = RazorDiagnosticFactory.CreateTagHelper_InlineMarkupBlocksNotSupportedInAttributes(expectedLocation, "System.Int32");

            // Act
            extension.RenderTagHelperAttributeInline(context, node, new TemplateIntermediateNode(), expectedLocation);

            // Assert
            var diagnostic = Assert.Single(context.Diagnostics);

            Assert.Equal(expectedDiagnostic, diagnostic);
        }
        public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node)
        {
            var tagHelperNode = context.Parent as TagHelperIntermediateNode;

            if (context.Parent == null)
            {
                var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
                throw new InvalidOperationException(message);
            }

            if (!context.Options.DesignTime)
            {
                // Ensure that the property we're trying to set has initialized its dictionary bound properties.
                if (node.IsIndexerNameMatch &&
                    object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node))
                {
                    // Throw a reasonable Exception at runtime if the dictionary property is null.
                    context.CodeWriter
                    .Write("if (")
                    .Write(node.FieldName)
                    .Write(".")
                    .Write(node.PropertyName)
                    .WriteLine(" == null)");
                    using (context.CodeWriter.BuildScope())
                    {
                        // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName
                        // of InvalidOperationException type.
                        context.CodeWriter
                        .Write("throw ")
                        .WriteStartNewObject(nameof(InvalidOperationException))
                        .WriteStartMethodInvocation(FormatInvalidIndexerAssignmentMethodName)
                        .WriteStringLiteral(node.AttributeName)
                        .WriteParameterSeparator()
                        .WriteStringLiteral(node.TagHelper.GetTypeName())
                        .WriteParameterSeparator()
                        .WriteStringLiteral(node.PropertyName)
                        .WriteEndMethodInvocation(endLine: false) // End of method call
                        .WriteEndMethodInvocation();              // End of new expression / throw statement
                    }
                }
            }

            // If this is not the first use of the attribute value, we need to evaluate the expression and assign it to
            // the tag helper property.
            //
            // Otherwise, the value has already been computed and assigned to another tag helper. We just need to
            // copy from that tag helper to this one.
            //
            // This is important because we can't evaluate the expression twice due to side-effects.
            var firstUseOfAttribute = FindFirstUseOfAttribute(tagHelperNode, node);

            if (!object.ReferenceEquals(firstUseOfAttribute, node))
            {
                // If we get here, this value has already been used. We just need to copy the value.
                context.CodeWriter
                .WriteStartAssignment(GetPropertyAccessor(node))
                .Write(GetPropertyAccessor(firstUseOfAttribute))
                .WriteLine(";");

                return;
            }

            // If we get there, this is the first time seeing this property so we need to evaluate the expression.
            if (node.BoundAttribute.ExpectsStringValue(node.AttributeName))
            {
                if (context.Options.DesignTime)
                {
                    context.RenderChildren(node);

                    context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
                    if (node.Children.Count == 1 && node.Children.First() is HtmlContentIntermediateNode htmlNode)
                    {
                        var content = GetContent(htmlNode);
                        context.CodeWriter.WriteStringLiteral(content);
                    }
                    else
                    {
                        context.CodeWriter.Write("string.Empty");
                    }
                    context.CodeWriter.WriteLine(";");
                }
                else
                {
                    context.CodeWriter.WriteMethodInvocation(BeginWriteTagHelperAttributeMethodName);

                    context.RenderChildren(node, new LiteralRuntimeNodeWriter());

                    context.CodeWriter
                    .WriteStartAssignment(StringValueBufferVariableName)
                    .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName)
                    .WriteStartAssignment(GetPropertyAccessor(node))
                    .Write(StringValueBufferVariableName)
                    .WriteLine(";");
                }
            }
            else
            {
                if (context.Options.DesignTime)
                {
                    var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode;
                    var valueStart       = firstMappedChild?.Source;

                    using (context.CodeWriter.BuildLinePragma(node.Source, context))
                    {
                        var accessor = GetPropertyAccessor(node);
                        var assignmentPrefixLength = accessor.Length + " = ".Length;
                        if (node.BoundAttribute.IsEnum &&
                            node.Children.Count == 1 &&
                            node.Children.First() is IntermediateToken token &&
                            token.IsCSharp)
                        {
                            assignmentPrefixLength += $"global::{node.BoundAttribute.TypeName}.".Length;

                            if (valueStart != null)
                            {
                                context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context);
                            }

                            context.CodeWriter
                            .WriteStartAssignment(accessor)
                            .Write("global::")
                            .Write(node.BoundAttribute.TypeName)
                            .Write(".");
                        }
                        else
                        {
                            if (valueStart != null)
                            {
                                context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context);
                            }

                            context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
                        }

                        if (node.Children.Count == 0 &&
                            node.AttributeStructure == AttributeStructure.Minimized &&
                            node.BoundAttribute.ExpectsBooleanValue(node.AttributeName))
                        {
                            // If this is a minimized boolean attribute, set the value to true.
                            context.CodeWriter.Write("true");
                        }
                        else
                        {
                            RenderTagHelperAttributeInline(context, node, node.Source);
                        }

                        context.CodeWriter.WriteLine(";");
                    }
                }