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);
    }
    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);
    }
    [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();
        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);
    }
    public void WriteTagHelperProperty_Runtime_NonStringProperty_SecondUseOfAttribute()
    {
        // Arrange
        var extension = new DefaultTagHelperTargetExtension();
        var context   = TestCodeRenderingContext.CreateRuntime();

        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 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(";");
                }
            }