Rewrite of code rendering context

This commit is contained in:
Ryan Nowak 2017-07-03 15:25:34 -07:00
parent 8dfba25d59
commit 7408bcd025
10 changed files with 489 additions and 108 deletions

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language.Intermediate; using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
@ -18,11 +19,15 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public abstract string DocumentKind { get; } public abstract string DocumentKind { get; }
public abstract ItemCollection Items { get; } public abstract ItemCollection Items { get; }
public abstract IEnumerable<IntermediateNode> Ancestors { get; }
public abstract IntermediateNodeWriter NodeWriter { get; } public abstract IntermediateNodeWriter NodeWriter { get; }
public abstract RazorCodeGenerationOptions Options { get; } public abstract RazorCodeGenerationOptions Options { get; }
public abstract IntermediateNode Parent { get; }
public abstract RazorSourceDocument SourceDocument { get; } public abstract RazorSourceDocument SourceDocument { get; }
public abstract Scope CreateScope(); public abstract Scope CreateScope();
@ -56,48 +61,5 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
_context.EndScope(); _context.EndScope();
} }
} }
// All bits below here are temporary
#region Temporary TagHelper bits
internal TagHelperRenderingContext TagHelperRenderingContext { get; set; }
internal TagHelperRenderingContextScope Push(TagHelperRenderingContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var scope = new TagHelperRenderingContextScope(this, TagHelperRenderingContext);
TagHelperRenderingContext = context;
return scope;
}
internal struct TagHelperRenderingContextScope : IDisposable
{
private readonly CodeRenderingContext _context;
private readonly TagHelperRenderingContext _renderingContext;
public TagHelperRenderingContextScope(CodeRenderingContext context, TagHelperRenderingContext renderingContext)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
_context = context;
_renderingContext = renderingContext;
}
public void Dispose()
{
_context.TagHelperRenderingContext = _renderingContext;
}
}
#endregion
} }
} }

View File

@ -10,9 +10,9 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{ {
internal class DefaultCodeRenderingContext : CodeRenderingContext internal class DefaultCodeRenderingContext : CodeRenderingContext
{ {
private readonly Stack<IntermediateNode> _ancestors;
private readonly RazorCodeDocument _codeDocument; private readonly RazorCodeDocument _codeDocument;
private readonly DocumentIntermediateNode _documentNode; private readonly DocumentIntermediateNode _documentNode;
private readonly List<ScopeInternal> _scopes; private readonly List<ScopeInternal> _scopes;
public DefaultCodeRenderingContext( public DefaultCodeRenderingContext(
@ -52,12 +52,11 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
_documentNode = documentNode; _documentNode = documentNode;
Options = options; Options = options;
_ancestors = new Stack<IntermediateNode>();
Diagnostics = new RazorDiagnosticCollection(); Diagnostics = new RazorDiagnosticCollection();
Items = new ItemCollection(); Items = new ItemCollection();
LineMappings = new List<LineMapping>(); LineMappings = new List<LineMapping>();
TagHelperRenderingContext = new TagHelperRenderingContext();
var diagnostics = _documentNode.GetAllDiagnostics(); var diagnostics = _documentNode.GetAllDiagnostics();
for (var i = 0; i < diagnostics.Count; i++) for (var i = 0; i < diagnostics.Count; i++)
{ {
@ -81,6 +80,10 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
// This will be initialized by the document writer when the context is 'live'. // This will be initialized by the document writer when the context is 'live'.
public IntermediateNodeVisitor Visitor { get; set; } public IntermediateNodeVisitor Visitor { get; set; }
public override IEnumerable<IntermediateNode> Ancestors => _ancestors;
internal Stack<IntermediateNode> AncestorsInternal => _ancestors;
public override CodeWriter CodeWriter { get; } public override CodeWriter CodeWriter { get; }
public override RazorDiagnosticCollection Diagnostics { get; } public override RazorDiagnosticCollection Diagnostics { get; }
@ -95,6 +98,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public override RazorCodeGenerationOptions Options { get; } public override RazorCodeGenerationOptions Options { get; }
public override IntermediateNode Parent => _ancestors.Count == 0 ? null : _ancestors.Peek();
public override RazorSourceDocument SourceDocument => _codeDocument.Source; public override RazorSourceDocument SourceDocument => _codeDocument.Source;
private ScopeInternal Current => _scopes[_scopes.Count - 1]; private ScopeInternal Current => _scopes[_scopes.Count - 1];
@ -154,10 +159,14 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
throw new ArgumentNullException(nameof(node)); throw new ArgumentNullException(nameof(node));
} }
_ancestors.Push(node);
for (var i = 0; i < node.Children.Count; i++) for (var i = 0; i < node.Children.Count; i++)
{ {
Visitor.Visit(node.Children[i]); Visitor.Visit(node.Children[i]);
} }
_ancestors.Pop();
} }
public override void RenderNode(IntermediateNode node) public override void RenderNode(IntermediateNode node)

View File

@ -200,12 +200,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public override void VisitTagHelper(TagHelperIntermediateNode node) public override void VisitTagHelper(TagHelperIntermediateNode node)
{ {
var tagHelperRenderingContext = new TagHelperRenderingContext(); VisitDefault(node);
using (Context.Push(tagHelperRenderingContext))
{
VisitDefault(node);
}
} }
public override void VisitDefault(IntermediateNode node) public override void VisitDefault(IntermediateNode node)

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@ -75,6 +76,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperBody(CodeRenderingContext context, DefaultTagHelperBodyIntermediateNode node) public void WriteTagHelperBody(CodeRenderingContext context, DefaultTagHelperBodyIntermediateNode node)
{ {
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (DesignTime) if (DesignTime)
{ {
context.RenderChildren(node); context.RenderChildren(node);
@ -121,6 +128,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperCreate(CodeRenderingContext context, DefaultTagHelperCreateIntermediateNode node) public void WriteTagHelperCreate(CodeRenderingContext context, DefaultTagHelperCreateIntermediateNode node)
{ {
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
context.CodeWriter context.CodeWriter
.WriteStartAssignment(node.Field) .WriteStartAssignment(node.Field)
.Write(CreateTagHelperMethodName) .Write(CreateTagHelperMethodName)
@ -137,6 +150,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperExecute(CodeRenderingContext context, DefaultTagHelperExecuteIntermediateNode node) public void WriteTagHelperExecute(CodeRenderingContext context, DefaultTagHelperExecuteIntermediateNode node)
{ {
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (!DesignTime) if (!DesignTime)
{ {
context.CodeWriter context.CodeWriter
@ -178,6 +197,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, DefaultTagHelperHtmlAttributeIntermediateNode node) public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, DefaultTagHelperHtmlAttributeIntermediateNode node)
{ {
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (DesignTime) if (DesignTime)
{ {
context.RenderChildren(node); context.RenderChildren(node);
@ -261,22 +286,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node) public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node)
{ {
var tagHelperRenderingContext = context.TagHelperRenderingContext; var tagHelperNode = context.Parent as TagHelperIntermediateNode;
var propertyName = node.BoundAttribute.GetPropertyName(); if (context.Parent == null)
var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, node.Field, node.AttributeName, node.BoundAttribute); {
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
if (!DesignTime) if (!DesignTime)
{ {
// Ensure that the property we're trying to set has initialized its dictionary bound properties. // Ensure that the property we're trying to set has initialized its dictionary bound properties.
if (node.IsIndexerNameMatch && if (node.IsIndexerNameMatch &&
tagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelper.GetTypeName()}.{propertyName}")) object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node))
{ {
// Throw a reasonable Exception at runtime if the dictionary property is null. // Throw a reasonable Exception at runtime if the dictionary property is null.
context.CodeWriter context.CodeWriter
.Write("if (") .Write("if (")
.Write(node.Field) .Write(node.Field)
.Write(".") .Write(".")
.Write(propertyName) .Write(node.Property)
.WriteLine(" == null)"); .WriteLine(" == null)");
using (context.CodeWriter.BuildScope()) using (context.CodeWriter.BuildScope())
{ {
@ -290,34 +318,40 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
.WriteParameterSeparator() .WriteParameterSeparator()
.WriteStringLiteral(node.TagHelper.GetTypeName()) .WriteStringLiteral(node.TagHelper.GetTypeName())
.WriteParameterSeparator() .WriteParameterSeparator()
.WriteStringLiteral(propertyName) .WriteStringLiteral(node.Property)
.WriteEndMethodInvocation(endLine: false) // End of method call .WriteEndMethodInvocation(endLine: false) // End of method call
.WriteEndMethodInvocation(); // End of new expression / throw statement .WriteEndMethodInvocation(); // End of new expression / throw statement
} }
} }
} }
if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out var previousValueAccessor)) // 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 context.CodeWriter
.WriteStartAssignment(propertyValueAccessor) .WriteStartAssignment(GetPropertyAccessor(node))
.Write(previousValueAccessor) .Write(GetPropertyAccessor(firstUseOfAttribute))
.WriteLine(";"); .WriteLine(";");
return; return;
} }
else
{
tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor;
}
// If we get there, this is the first time seeing this property so we need to evaluate the expression.
if (node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty)) if (node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty))
{ {
if (DesignTime) if (DesignTime)
{ {
context.RenderChildren(node); context.RenderChildren(node);
context.CodeWriter.WriteStartAssignment(propertyValueAccessor); context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
if (node.Children.Count == 1 && node.Children.First() is HtmlContentIntermediateNode htmlNode) if (node.Children.Count == 1 && node.Children.First() is HtmlContentIntermediateNode htmlNode)
{ {
var content = GetContent(htmlNode); var content = GetContent(htmlNode);
@ -341,7 +375,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
context.CodeWriter context.CodeWriter
.WriteStartAssignment(StringValueBufferVariableName) .WriteStartAssignment(StringValueBufferVariableName)
.WriteMethodInvocation(EndWriteTagHelperAttributeMethodName) .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName)
.WriteStartAssignment(propertyValueAccessor) .WriteStartAssignment(GetPropertyAccessor(node))
.Write(StringValueBufferVariableName) .Write(StringValueBufferVariableName)
.WriteLine(";"); .WriteLine(";");
} }
@ -355,7 +389,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
using (context.CodeWriter.BuildLinePragma(node.Source)) using (context.CodeWriter.BuildLinePragma(node.Source))
{ {
var assignmentPrefixLength = propertyValueAccessor.Length + " = ".Length; var accessor = GetPropertyAccessor(node);
var assignmentPrefixLength = accessor.Length + " = ".Length;
if (node.BoundAttribute.IsEnum && if (node.BoundAttribute.IsEnum &&
node.Children.Count == 1 && node.Children.Count == 1 &&
node.Children.First() is IntermediateToken token && node.Children.First() is IntermediateToken token &&
@ -369,7 +404,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
} }
context.CodeWriter context.CodeWriter
.WriteStartAssignment(propertyValueAccessor) .WriteStartAssignment(accessor)
.Write("global::") .Write("global::")
.Write(node.BoundAttribute.TypeName) .Write(node.BoundAttribute.TypeName)
.Write("."); .Write(".");
@ -381,7 +416,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context); context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context);
} }
context.CodeWriter.WriteStartAssignment(propertyValueAccessor); context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
} }
RenderTagHelperAttributeInline(context, node, node.Source); RenderTagHelperAttributeInline(context, node, node.Source);
@ -393,7 +428,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
{ {
using (context.CodeWriter.BuildLinePragma(node.Source)) using (context.CodeWriter.BuildLinePragma(node.Source))
{ {
context.CodeWriter.WriteStartAssignment(propertyValueAccessor); context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node));
if (node.BoundAttribute.IsEnum && if (node.BoundAttribute.IsEnum &&
node.Children.Count == 1 && node.Children.Count == 1 &&
@ -422,7 +457,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
ExecutionContextAddTagHelperAttributeMethodName) ExecutionContextAddTagHelperAttributeMethodName)
.WriteStringLiteral(node.AttributeName) .WriteStringLiteral(node.AttributeName)
.WriteParameterSeparator() .WriteParameterSeparator()
.Write(propertyValueAccessor) .Write(GetPropertyAccessor(node))
.WriteParameterSeparator() .WriteParameterSeparator()
.Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}") .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}")
.WriteEndMethodInvocation(); .WriteEndMethodInvocation();
@ -548,6 +583,45 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
} }
} }
private static DefaultTagHelperPropertyIntermediateNode FindFirstUseOfIndexer(
TagHelperIntermediateNode tagHelperNode,
DefaultTagHelperPropertyIntermediateNode propertyNode)
{
Debug.Assert(tagHelperNode.Children.Contains(propertyNode));
Debug.Assert(propertyNode.IsIndexerNameMatch);
for (var i = 0; i < tagHelperNode.Children.Count; i++)
{
if (tagHelperNode.Children[i] is DefaultTagHelperPropertyIntermediateNode otherPropertyNode &&
otherPropertyNode.TagHelper.Equals(propertyNode.TagHelper) &&
otherPropertyNode.BoundAttribute.Equals(propertyNode.BoundAttribute) &&
otherPropertyNode.IsIndexerNameMatch)
{
return otherPropertyNode;
}
}
// This is unreachable, we should find 'propertyNode' in the list of children.
throw new InvalidOperationException();
}
private static DefaultTagHelperPropertyIntermediateNode FindFirstUseOfAttribute(
TagHelperIntermediateNode tagHelperNode,
DefaultTagHelperPropertyIntermediateNode propertyNode)
{
for (var i = 0; i < tagHelperNode.Children.Count; i++)
{
if (tagHelperNode.Children[i] is DefaultTagHelperPropertyIntermediateNode otherPropertyNode &&
string.Equals(otherPropertyNode.AttributeName, propertyNode.AttributeName, StringComparison.Ordinal))
{
return otherPropertyNode;
}
}
// This is unreachable, we should find 'propertyNode' in the list of children.
throw new InvalidOperationException();
}
private string GetContent(HtmlContentIntermediateNode node) private string GetContent(HtmlContentIntermediateNode node)
{ {
var builder = new StringBuilder(); var builder = new StringBuilder();
@ -562,17 +636,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
return builder.ToString(); return builder.ToString();
} }
private static string GetTagHelperPropertyAccessor( private static string GetPropertyAccessor(DefaultTagHelperPropertyIntermediateNode node)
bool isIndexerNameMatch,
string tagHelperVariableName,
string attributeName,
BoundAttributeDescriptor descriptor)
{ {
var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; var propertyAccessor = $"{node.Field}.{node.Property}";
if (isIndexerNameMatch) if (node.IsIndexerNameMatch)
{ {
var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length); var dictionaryKey = node.AttributeName.Substring(node.BoundAttribute.IndexerNamePrefix.Length);
propertyAccessor += $"[\"{dictionaryKey}\"]"; propertyAccessor += $"[\"{dictionaryKey}\"]";
} }

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.Extensions namespace Microsoft.AspNetCore.Razor.Language.Extensions
{ {
@ -50,6 +52,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeIntermediateNode node) public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeIntermediateNode node)
{ {
if (context.Parent as TagHelperIntermediateNode == null)
{
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
context.CodeWriter context.CodeWriter
.WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddHtmlAttributeMethodName) .WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddHtmlAttributeMethodName)
.Write(node.VariableName) .Write(node.VariableName)
@ -75,20 +83,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public void WriteTagHelperProperty(CodeRenderingContext context, PreallocatedTagHelperPropertyIntermediateNode node) public void WriteTagHelperProperty(CodeRenderingContext context, PreallocatedTagHelperPropertyIntermediateNode node)
{ {
var propertyName = node.BoundAttribute.GetPropertyName(); var tagHelperNode = context.Parent as TagHelperIntermediateNode;
var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, node.Field, node.AttributeName, node.BoundAttribute); if (tagHelperNode == null)
var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */; {
var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode));
throw new InvalidOperationException(message);
}
// Ensure that the property we're trying to set has initialized its dictionary bound properties. // Ensure that the property we're trying to set has initialized its dictionary bound properties.
if (node.IsIndexerNameMatch && if (node.IsIndexerNameMatch &&
context.TagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelper.GetTypeName()}.{propertyName}")) object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node))
{ {
// Throw a reasonable Exception at runtime if the dictionary property is null. // Throw a reasonable Exception at runtime if the dictionary property is null.
context.CodeWriter context.CodeWriter
.Write("if (") .Write("if (")
.Write(node.Field) .Write(node.Field)
.Write(".") .Write(".")
.Write(propertyName) .Write(node.Property)
.WriteLine(" == null)"); .WriteLine(" == null)");
using (context.CodeWriter.BuildScope()) using (context.CodeWriter.BuildScope())
{ {
@ -102,33 +113,51 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
.WriteParameterSeparator() .WriteParameterSeparator()
.WriteStringLiteral(node.TagHelper.GetTypeName()) .WriteStringLiteral(node.TagHelper.GetTypeName())
.WriteParameterSeparator() .WriteParameterSeparator()
.WriteStringLiteral(propertyName) .WriteStringLiteral(node.Property)
.WriteEndMethodInvocation(endLine: false) // End of method call .WriteEndMethodInvocation(endLine: false) // End of method call
.WriteEndMethodInvocation(); // End of new expression / throw statement .WriteEndMethodInvocation(); // End of new expression / throw statement
} }
} }
context.CodeWriter context.CodeWriter
.WriteStartAssignment(propertyValueAccessor) .WriteStartAssignment(GetPropertyAccessor(node))
.Write("(string)") .Write("(string)")
.Write(attributeValueAccessor) .Write($"{node.VariableName}.Value")
.WriteLine(";") .WriteLine(";")
.WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddTagHelperAttributeMethodName) .WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddTagHelperAttributeMethodName)
.Write(node.VariableName) .Write(node.VariableName)
.WriteEndMethodInvocation(); .WriteEndMethodInvocation();
} }
private static string GetTagHelperPropertyAccessor( private static PreallocatedTagHelperPropertyIntermediateNode FindFirstUseOfIndexer(
bool isIndexerNameMatch, TagHelperIntermediateNode tagHelperNode,
string tagHelperVariableName, PreallocatedTagHelperPropertyIntermediateNode propertyNode)
string attributeName,
BoundAttributeDescriptor descriptor)
{ {
var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; Debug.Assert(tagHelperNode.Children.Contains(propertyNode));
Debug.Assert(propertyNode.IsIndexerNameMatch);
if (isIndexerNameMatch) for (var i = 0; i < tagHelperNode.Children.Count; i++)
{ {
var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length); if (tagHelperNode.Children[i] is PreallocatedTagHelperPropertyIntermediateNode otherPropertyNode &&
otherPropertyNode.TagHelper.Equals(propertyNode.TagHelper) &&
otherPropertyNode.BoundAttribute.Equals(propertyNode.BoundAttribute) &&
otherPropertyNode.IsIndexerNameMatch)
{
return otherPropertyNode;
}
}
// This is unreachable, we should find 'propertyNode' in the list of children.
throw new InvalidOperationException();
}
private static string GetPropertyAccessor(PreallocatedTagHelperPropertyIntermediateNode node)
{
var propertyAccessor = $"{node.Field}.{node.Property}";
if (node.IsIndexerNameMatch)
{
var dictionaryKey = node.AttributeName.Substring(node.BoundAttribute.IndexerNamePrefix.Length);
propertyAccessor += $"[\"{dictionaryKey}\"]"; propertyAccessor += $"[\"{dictionaryKey}\"]";
} }

View File

@ -584,6 +584,20 @@ namespace Microsoft.AspNetCore.Razor.Language
internal static string FormatRenderingContextRequiresDelegate(object p0, object p1) internal static string FormatRenderingContextRequiresDelegate(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("RenderingContextRequiresDelegate"), p0, p1); => string.Format(CultureInfo.CurrentCulture, GetString("RenderingContextRequiresDelegate"), p0, p1);
/// <summary>
/// The '{0}' node type can only be used as a direct child of a '{1}' node.
/// </summary>
internal static string IntermediateNodes_InvalidParentNode
{
get => GetString("IntermediateNodes_InvalidParentNode");
}
/// <summary>
/// The '{0}' node type can only be used as a direct child of a '{1}' node.
/// </summary>
internal static string FormatIntermediateNodes_InvalidParentNode(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("IntermediateNodes_InvalidParentNode"), p0, p1);
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -240,4 +240,7 @@
<data name="RenderingContextRequiresDelegate" xml:space="preserve"> <data name="RenderingContextRequiresDelegate" xml:space="preserve">
<value>The '{0}' requires a '{1}' delegate to be set.</value> <value>The '{0}' requires a '{1}' delegate to be set.</value>
</data> </data>
<data name="IntermediateNodes_InvalidParentNode" xml:space="preserve">
<value>The '{0}' node type can only be used as a direct child of a '{1}' node.</value>
</data>
</root> </root>

View File

@ -71,6 +71,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperBodyIntermediateNode() var node = new DefaultTagHelperBodyIntermediateNode()
{ {
Children = Children =
@ -78,6 +79,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
new CSharpExpressionIntermediateNode(), new CSharpExpressionIntermediateNode(),
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperBody(context, node); extension.WriteTagHelperBody(context, node);
@ -98,6 +101,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperBodyIntermediateNode() var node = new DefaultTagHelperBodyIntermediateNode()
{ {
Children = Children =
@ -107,6 +111,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
TagMode = TagMode.SelfClosing, TagMode = TagMode.SelfClosing,
TagName = "p", TagName = "p",
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperBody(context, node); extension.WriteTagHelperBody(context, node);
@ -130,11 +136,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperCreateIntermediateNode() var node = new DefaultTagHelperCreateIntermediateNode()
{ {
Field = "__TestNamespace_MyTagHelper", Field = "__TestNamespace_MyTagHelper",
Type = "TestNamespace.MyTagHelper", Type = "TestNamespace.MyTagHelper",
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperCreate(context, node); extension.WriteTagHelperCreate(context, node);
@ -155,11 +164,14 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperCreateIntermediateNode() var node = new DefaultTagHelperCreateIntermediateNode()
{ {
Field = "__TestNamespace_MyTagHelper", Field = "__TestNamespace_MyTagHelper",
Type = "TestNamespace.MyTagHelper", Type = "TestNamespace.MyTagHelper",
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperCreate(context, node); extension.WriteTagHelperCreate(context, node);
@ -181,7 +193,10 @@ __tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper);
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperExecuteIntermediateNode(); var node = new DefaultTagHelperExecuteIntermediateNode();
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperExecute(context, node); extension.WriteTagHelperExecute(context, node);
@ -201,7 +216,10 @@ __tagHelperExecutionContext.Add(__TestNamespace_MyTagHelper);
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperExecuteIntermediateNode(); var node = new DefaultTagHelperExecuteIntermediateNode();
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperExecute(context, node); extension.WriteTagHelperExecute(context, node);
@ -228,6 +246,7 @@ __tagHelperExecutionContext = __tagHelperScopeManager.End();
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperHtmlAttributeIntermediateNode() var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{ {
AttributeName = "name", AttributeName = "name",
@ -244,6 +263,8 @@ __tagHelperExecutionContext = __tagHelperScopeManager.End();
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperHtmlAttribute(context, node); extension.WriteTagHelperHtmlAttribute(context, node);
@ -265,6 +286,7 @@ Render Children
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperHtmlAttributeIntermediateNode() var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{ {
AttributeName = "name", AttributeName = "name",
@ -277,6 +299,8 @@ Render Children
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperHtmlAttribute(context, node); extension.WriteTagHelperHtmlAttribute(context, node);
@ -300,6 +324,7 @@ __tagHelperExecutionContext.AddHtmlAttribute(""name"", Html.Raw(__tagHelperStrin
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperHtmlAttributeIntermediateNode() var node = new DefaultTagHelperHtmlAttributeIntermediateNode()
{ {
AttributeName = "name", AttributeName = "name",
@ -316,6 +341,8 @@ __tagHelperExecutionContext.AddHtmlAttribute(""name"", Html.Raw(__tagHelperStrin
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperHtmlAttribute(context, node); extension.WriteTagHelperHtmlAttribute(context, node);
@ -339,6 +366,7 @@ EndAddHtmlAttributeValues(__tagHelperExecutionContext);
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -356,6 +384,8 @@ EndAddHtmlAttributeValues(__tagHelperExecutionContext);
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -377,6 +407,7 @@ __InputTagHelper.StringProp = ""value"";
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -394,6 +425,8 @@ __InputTagHelper.StringProp = ""value"";
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -415,6 +448,7 @@ __InputTagHelper.StringProp = string.Empty;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -433,6 +467,8 @@ __InputTagHelper.StringProp = string.Empty;
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -450,6 +486,50 @@ __InputTagHelper.IntProp = 32;
ignoreLineEndingDifferences: true); ignoreLineEndingDifferences: true);
} }
// If a value is bound to multiple tag helpers, we want to make sure to only render the first
// occurrence of the expression due to side-effects.
[Fact]
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",
Field = "__OtherTagHelper",
Property = "IntProp",
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = false,
Property = "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.Builder.ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact] [Fact]
public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly_WithoutLocation() public void WriteTagHelperProperty_DesignTime_NonStringProperty_RendersCorrectly_WithoutLocation()
{ {
@ -457,6 +537,7 @@ __InputTagHelper.IntProp = 32;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -474,6 +555,8 @@ __InputTagHelper.IntProp = 32;
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -494,6 +577,7 @@ __InputTagHelper.IntProp = 32;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "foo-bound", AttributeName = "foo-bound",
@ -512,6 +596,8 @@ __InputTagHelper.IntProp = 32;
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -536,6 +622,7 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "foo-bound", AttributeName = "foo-bound",
@ -553,6 +640,8 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -573,6 +662,7 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -590,6 +680,8 @@ __InputTagHelper.IntIndexer[""bound""] = 32;
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -616,6 +708,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.St
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -634,6 +727,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.St
} }
}, },
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -652,6 +747,50 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
ignoreLineEndingDifferences: true); ignoreLineEndingDifferences: true);
} }
// If a value is bound to multiple tag helpers, we want to make sure to only render the first
// occurrence of the expression due to side-effects.
[Fact]
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",
Field = "__OtherTagHelper",
Property = "IntProp",
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = false,
Property = "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.Builder.ToString();
Assert.Equal(
@"__InputTagHelper.IntProp = __OtherTagHelper.IntProp;
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact] [Fact]
public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_WithoutLocation() public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_WithoutLocation()
{ {
@ -659,6 +798,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "bound", AttributeName = "bound",
@ -676,6 +816,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -697,6 +839,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "foo-bound", AttributeName = "foo-bound",
@ -715,6 +858,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -729,6 +874,71 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.In
#line 3 ""test.cshtml"" #line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32; __InputTagHelper.IntIndexer[""bound""] = 32;
#line default
#line hidden
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
",
csharp,
ignoreLineEndingDifferences: true);
}
[Fact] // We should only emit the validation code for the first use of an indexer property.
public void WriteTagHelperProperty_Runtime_NonStringIndexer_MultipleValues()
{
// Arrange
var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-first",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = true,
Property = "IntIndexer",
TagHelper = IntIndexerTagHelper,
Source = Span,
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { new IntermediateToken { Kind = IntermediateToken.TokenKind.CSharp, Content = "17", } },
}
}
};
var node2 = new DefaultTagHelperPropertyIntermediateNode()
{
AttributeName = "foo-bound",
AttributeStructure = AttributeStructure.DoubleQuotes,
BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(),
Field = "__InputTagHelper",
IsIndexerNameMatch = true,
Property = "IntIndexer",
TagHelper = IntIndexerTagHelper,
Source = Span,
Children =
{
new CSharpExpressionIntermediateNode()
{
Children = { new IntermediateToken { Kind = IntermediateToken.TokenKind.CSharp, Content = "32", } },
}
}
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.Builder.ToString();
Assert.Equal(
@"#line 3 ""test.cshtml""
__InputTagHelper.IntIndexer[""bound""] = 32;
#line default #line default
#line hidden #line hidden
__tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
@ -744,6 +954,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new DefaultTagHelperPropertyIntermediateNode() var node = new DefaultTagHelperPropertyIntermediateNode()
{ {
AttributeName = "foo-bound", AttributeName = "foo-bound",
@ -761,6 +972,8 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
} }
} }
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -785,7 +998,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
// Arrange // Arrange
var extension = new DefaultTagHelperTargetExtension() { DesignTime = true }; var extension = new DefaultTagHelperTargetExtension() { DesignTime = true };
var context = TestCodeRenderingContext.CreateDesignTime(); var context = TestCodeRenderingContext.CreateDesignTime();
var node = new DefaultTagHelperRuntimeIntermediateNode(); var node = new DefaultTagHelperRuntimeIntermediateNode();
// Act // Act
@ -805,7 +1018,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelpe
// Arrange // Arrange
var extension = new DefaultTagHelperTargetExtension(); var extension = new DefaultTagHelperTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var node = new DefaultTagHelperRuntimeIntermediateNode(); var node = new DefaultTagHelperRuntimeIntermediateNode();
// Act // Act
@ -837,6 +1050,11 @@ private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeMana
ignoreLineEndingDifferences: true); ignoreLineEndingDifferences: true);
} }
private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node)
{
((DefaultCodeRenderingContext)context).AncestorsInternal.Push(node);
}
private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument) private static DocumentIntermediateNode Lower(RazorCodeDocument codeDocument)
{ {
var engine = RazorEngine.Create(); var engine = RazorEngine.Create();

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Razor.Language.CodeGeneration; using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Extensions namespace Microsoft.AspNetCore.Razor.Language.Extensions
@ -9,7 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
public class PreallocatedAttributeTargetExtensionTest public class PreallocatedAttributeTargetExtensionTest
{ {
[Fact] [Fact]
public void WriteDeclarePreallocatedTagHelperHtmlAttribute_RendersCorrectly() public void WriteTagHelperHtmlAttributeValue_RendersCorrectly()
{ {
// Arrange // Arrange
var extension = new PreallocatedAttributeTargetExtension(); var extension = new PreallocatedAttributeTargetExtension();
@ -36,7 +37,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
} }
[Fact] [Fact]
public void WriteDeclarePreallocatedTagHelperHtmlAttribute_Minimized_RendersCorrectly() public void WriteTagHelperHtmlAttributeValue_Minimized_RendersCorrectly()
{ {
// Arrange // Arrange
var extension = new PreallocatedAttributeTargetExtension(); var extension = new PreallocatedAttributeTargetExtension();
@ -63,16 +64,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
} }
[Fact] [Fact]
public void WriteAddPreallocatedTagHelperHtmlAttribute_RendersCorrectly() public void WriteTagHelperHtmlAttribute_RendersCorrectly()
{ {
// Arrange // Arrange
var extension = new PreallocatedAttributeTargetExtension(); var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperHtmlAttributeIntermediateNode() var node = new PreallocatedTagHelperHtmlAttributeIntermediateNode()
{ {
VariableName = "_tagHelper1" VariableName = "_tagHelper1"
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperHtmlAttribute(context, node); extension.WriteTagHelperHtmlAttribute(context, node);
@ -87,12 +91,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
} }
[Fact] [Fact]
public void WriteDeclarePreallocatedTagHelperAttribute_RendersCorrectly() public void WriteTagHelperPropertyValue_RendersCorrectly()
{ {
// Arrange // Arrange
var extension = new PreallocatedAttributeTargetExtension(); var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime(); var context = TestCodeRenderingContext.CreateRuntime();
var node = new PreallocatedTagHelperPropertyValueIntermediateNode() var node = new PreallocatedTagHelperPropertyValueIntermediateNode()
{ {
AttributeName = "Foo", AttributeName = "Foo",
@ -114,7 +118,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
} }
[Fact] [Fact]
public void WriteSetPreallocatedTagHelperProperty_RendersCorrectly() public void WriteTagHelperProperty_RendersCorrectly()
{ {
// Arrange // Arrange
var extension = new PreallocatedAttributeTargetExtension(); var extension = new PreallocatedAttributeTargetExtension();
@ -124,7 +128,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
tagHelperBuilder.TypeName("FooTagHelper"); tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind); var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder builder
.Name("Foo") .Name("Foo")
.TypeName("System.String") .TypeName("System.String")
@ -132,13 +135,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions
var descriptor = builder.Build(); var descriptor = builder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperPropertyIntermediateNode() var node = new PreallocatedTagHelperPropertyIntermediateNode()
{ {
AttributeName = descriptor.Name, AttributeName = descriptor.Name,
Field = "__FooTagHelper",
VariableName = "_tagHelper1",
BoundAttribute = descriptor, BoundAttribute = descriptor,
Field = "__FooTagHelper",
Property = "FooProp",
VariableName = "_tagHelper1",
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -164,7 +171,6 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
tagHelperBuilder.TypeName("FooTagHelper"); tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind); var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder builder
.Name("Foo") .Name("Foo")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.String>") .TypeName("System.Collections.Generic.Dictionary<System.String, System.String>")
@ -173,6 +179,7 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
var descriptor = builder.Build(); var descriptor = builder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node = new PreallocatedTagHelperPropertyIntermediateNode() var node = new PreallocatedTagHelperPropertyIntermediateNode()
{ {
AttributeName = "pre-Foo", AttributeName = "pre-Foo",
@ -180,8 +187,11 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
VariableName = "_tagHelper1", VariableName = "_tagHelper1",
BoundAttribute = descriptor, BoundAttribute = descriptor,
IsIndexerNameMatch = true, IsIndexerNameMatch = true,
Property = "FooProp",
TagHelper = tagHelperBuilder.Build(), TagHelper = tagHelperBuilder.Build(),
}; };
tagHelperNode.Children.Add(node);
Push(context, tagHelperNode);
// Act // Act
extension.WriteTagHelperProperty(context, node); extension.WriteTagHelperProperty(context, node);
@ -199,5 +209,68 @@ __tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
csharp, csharp,
ignoreLineEndingDifferences: true); ignoreLineEndingDifferences: true);
} }
[Fact]
public void WriteSetPreallocatedTagHelperProperty_IndexerAttribute_MultipleValues()
{
// Arrange
var extension = new PreallocatedAttributeTargetExtension();
var context = TestCodeRenderingContext.CreateRuntime();
var tagHelperBuilder = new DefaultTagHelperDescriptorBuilder(TagHelperConventions.DefaultKind, "FooTagHelper", "Test");
tagHelperBuilder.TypeName("FooTagHelper");
var builder = new DefaultBoundAttributeDescriptorBuilder(tagHelperBuilder, TagHelperConventions.DefaultKind);
builder
.Name("Foo")
.TypeName("System.Collections.Generic.Dictionary<System.String, System.String>")
.AsDictionaryAttribute("pre-", "System.String")
.PropertyName("FooProp");
var boundAttribute = builder.Build();
var tagHelper = tagHelperBuilder.Build();
var tagHelperNode = new TagHelperIntermediateNode();
var node1 = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Bar",
Field = "__FooTagHelper",
VariableName = "_tagHelper0s",
BoundAttribute = boundAttribute,
IsIndexerNameMatch = true,
Property = "FooProp",
TagHelper = tagHelper,
};
var node2 = new PreallocatedTagHelperPropertyIntermediateNode()
{
AttributeName = "pre-Foo",
Field = "__FooTagHelper",
VariableName = "_tagHelper1",
BoundAttribute = boundAttribute,
IsIndexerNameMatch = true,
Property = "FooProp",
TagHelper = tagHelper,
};
tagHelperNode.Children.Add(node1);
tagHelperNode.Children.Add(node2);
Push(context, tagHelperNode);
// Act
extension.WriteTagHelperProperty(context, node2);
// Assert
var csharp = context.CodeWriter.Builder.ToString();
Assert.Equal(
@"__FooTagHelper.FooProp[""Foo""] = (string)_tagHelper1.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(_tagHelper1);
",
csharp,
ignoreLineEndingDifferences: true);
}
private static void Push(CodeRenderingContext context, TagHelperIntermediateNode node)
{
((DefaultCodeRenderingContext)context).AncestorsInternal.Push(node);
}
} }
} }

View File

@ -181,6 +181,10 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["pepper"] = 98;
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5); __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5);
__TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = (string)__tagHelperAttribute_5.Value; __TestNamespace_InputTagHelper2.StringDictionaryProperty["paprika"] = (string)__tagHelperAttribute_5.Value;
__tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5); __tagHelperExecutionContext.AddTagHelperAttribute(__tagHelperAttribute_5);
if (__TestNamespace_InputTagHelper1.StringDictionaryProperty == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-cumin", "TestNamespace.InputTagHelper1", "StringDictionaryProperty"));
}
BeginWriteTagHelperAttribute(); BeginWriteTagHelperAttribute();
WriteLiteral("literate "); WriteLiteral("literate ");
#line 21 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers.cshtml" #line 21 "TestFiles/IntegrationTests/CodeGenerationIntegrationTest/PrefixedAttributeTagHelpers.cshtml"
@ -192,6 +196,10 @@ __TestNamespace_InputTagHelper1.IntDictionaryProperty["pepper"] = 98;
__tagHelperStringValueBuffer = EndWriteTagHelperAttribute(); __tagHelperStringValueBuffer = EndWriteTagHelperAttribute();
__TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"] = __tagHelperStringValueBuffer; __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"] = __tagHelperStringValueBuffer;
__tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-cumin", __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); __tagHelperExecutionContext.AddTagHelperAttribute("string-prefix-cumin", __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes);
if (__TestNamespace_InputTagHelper2.StringDictionaryProperty == null)
{
throw new InvalidOperationException(InvalidTagHelperIndexerAssignment("string-prefix-cumin", "TestNamespace.InputTagHelper2", "StringDictionaryProperty"));
}
__TestNamespace_InputTagHelper2.StringDictionaryProperty["cumin"] = __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"]; __TestNamespace_InputTagHelper2.StringDictionaryProperty["cumin"] = __TestNamespace_InputTagHelper1.StringDictionaryProperty["cumin"];
await __tagHelperRunner.RunAsync(__tagHelperExecutionContext); await __tagHelperRunner.RunAsync(__tagHelperExecutionContext);
if (!__tagHelperExecutionContext.Output.IsContentModified) if (!__tagHelperExecutionContext.Output.IsContentModified)