In Razor compiler, support attributes with interpolated expressions
This commit is contained in:
parent
b690aeee28
commit
3a7b6b2178
|
|
@ -29,7 +29,7 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private string _unconsumedHtml;
|
private string _unconsumedHtml;
|
||||||
private string _currentAttributeName;
|
private IList<object> _currentAttributeValues;
|
||||||
private IDictionary<string, object> _currentElementAttributes = new Dictionary<string, object>();
|
private IDictionary<string, object> _currentElementAttributes = new Dictionary<string, object>();
|
||||||
|
|
||||||
public override void BeginWriterScope(CodeRenderingContext context, string writer)
|
public override void BeginWriterScope(CodeRenderingContext context, string writer)
|
||||||
|
|
@ -105,24 +105,40 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
|
|
||||||
public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node)
|
public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(_currentAttributeName))
|
if (_currentAttributeValues == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Invoked {nameof(WriteCSharpCodeAttributeValue)} while {nameof(_currentAttributeName)} was null or empty.");
|
throw new InvalidOperationException($"Invoked {nameof(WriteCSharpCodeAttributeValue)} while {nameof(_currentAttributeValues)} was null.");
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentElementAttributes[_currentAttributeName] = node.Children.Single();
|
// In cases like "somestring @variable", Razor tokenizes it as:
|
||||||
|
// [0] HtmlContent="somestring"
|
||||||
|
// [1] CsharpContent="variable" Prefix=" "
|
||||||
|
// ... so to avoid losing whitespace, convert the prefix to a further token in the list
|
||||||
|
if (!string.IsNullOrEmpty(node.Prefix))
|
||||||
|
{
|
||||||
|
_currentAttributeValues.Add(node.Prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentAttributeValues.Add((IntermediateToken)node.Children.Single());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
||||||
{
|
{
|
||||||
_currentAttributeName = node.AttributeName;
|
_currentAttributeValues = new List<object>();
|
||||||
context.RenderChildren(node);
|
context.RenderChildren(node);
|
||||||
_currentAttributeName = null;
|
_currentElementAttributes[node.AttributeName] = _currentAttributeValues;
|
||||||
|
_currentAttributeValues = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlAttributeValueIntermediateNode node)
|
public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlAttributeValueIntermediateNode node)
|
||||||
{
|
{
|
||||||
throw new System.NotImplementedException(nameof(WriteHtmlAttributeValue));
|
if (_currentAttributeValues == null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Invoked {nameof(WriteHtmlAttributeValue)} while {nameof(_currentAttributeValues)} was null.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringContent = ((IntermediateToken)node.Children.Single()).Content;
|
||||||
|
_currentAttributeValues.Add(node.Prefix + stringContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node)
|
public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node)
|
||||||
|
|
@ -208,35 +224,11 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
|
|
||||||
private static void WriteAttribute(CodeWriter codeWriter, string key, object value)
|
private static void WriteAttribute(CodeWriter codeWriter, string key, object value)
|
||||||
{
|
{
|
||||||
if (value == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
codeWriter
|
codeWriter
|
||||||
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
||||||
.WriteStringLiteral(key)
|
.WriteStringLiteral(key)
|
||||||
.WriteParameterSeparator();
|
.WriteParameterSeparator();
|
||||||
|
WriteAttributeValue(codeWriter, value);
|
||||||
switch (value)
|
|
||||||
{
|
|
||||||
case IntermediateToken intermediateToken:
|
|
||||||
{
|
|
||||||
if (!intermediateToken.IsCSharp)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Not yet supported: IntermediateToken where IsCSharp==false");
|
|
||||||
}
|
|
||||||
|
|
||||||
codeWriter.Write(intermediateToken.Content);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case string valueString:
|
|
||||||
codeWriter.WriteStringLiteral(valueString);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new ArgumentException($"Unsupported attribute value type: {value.GetType().FullName}");
|
|
||||||
}
|
|
||||||
|
|
||||||
codeWriter.WriteEndMethodInvocation();
|
codeWriter.WriteEndMethodInvocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,5 +247,52 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
||||||
}
|
}
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void WriteAttributeValue(CodeWriter writer, object value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case string valueString:
|
||||||
|
writer.WriteStringLiteral(valueString);
|
||||||
|
break;
|
||||||
|
case IntermediateToken token:
|
||||||
|
{
|
||||||
|
if (token.IsCSharp)
|
||||||
|
{
|
||||||
|
writer.Write(token.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteStringLiteral(token.Content);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case IEnumerable<object> concatenatedValues:
|
||||||
|
{
|
||||||
|
var first = true;
|
||||||
|
foreach (var concatenatedValue in concatenatedValues)
|
||||||
|
{
|
||||||
|
if (first)
|
||||||
|
{
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.Write(" + ");
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteAttributeValue(writer, concatenatedValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ArgumentException($"Unsupported attribute value type: {value.GetType().FullName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,18 @@ namespace Microsoft.Blazor.RenderTree
|
||||||
Append(RenderTreeNode.Attribute(name, value));
|
Append(RenderTreeNode.Attribute(name, value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Appends a node representing a string-valued attribute.
|
||||||
|
/// The attribute is associated with the most recently added element.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="name">The name of the attribute.</param>
|
||||||
|
/// <param name="value">The value of the attribute.</param>
|
||||||
|
public void AddAttribute(string name, object value)
|
||||||
|
{
|
||||||
|
AssertCanAddAttribute();
|
||||||
|
Append(RenderTreeNode.Attribute(name, value.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Appends a node representing a child component.
|
/// Appends a node representing a child component.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,34 @@ namespace Microsoft.Blazor.Build.Test
|
||||||
node => AssertNode.Attribute(node, "attr", "My string"));
|
node => AssertNode.Attribute(node, "attr", "My string"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SupportsAttributesWithNonStringExpressionValues()
|
||||||
|
{
|
||||||
|
// Arrange/Act
|
||||||
|
var component = CompileToComponent(
|
||||||
|
"@{ var myValue = 123; }"
|
||||||
|
+ "<elem attr=@myValue />");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(GetRenderTree(component).Where(NotWhitespace),
|
||||||
|
node => AssertNode.Element(node, "elem", 1),
|
||||||
|
node => AssertNode.Attribute(node, "attr", "123"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SupportsAttributesWithInterpolatedStringExpressionValues()
|
||||||
|
{
|
||||||
|
// Arrange/Act
|
||||||
|
var component = CompileToComponent(
|
||||||
|
"@{ var myValue = \"world\"; var myNum=123; }"
|
||||||
|
+ "<elem attr=\"Hello, @myValue.ToUpperInvariant() with number @(myNum*2)!\" />");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(GetRenderTree(component).Where(NotWhitespace),
|
||||||
|
node => AssertNode.Element(node, "elem", 1),
|
||||||
|
node => AssertNode.Attribute(node, "attr", "Hello, WORLD with number 246!"));
|
||||||
|
}
|
||||||
|
|
||||||
private static bool NotWhitespace(RenderTreeNode node)
|
private static bool NotWhitespace(RenderTreeNode node)
|
||||||
=> node.NodeType != RenderTreeNodeType.Text
|
=> node.NodeType != RenderTreeNodeType.Text
|
||||||
|| !string.IsNullOrWhiteSpace(node.TextContent);
|
|| !string.IsNullOrWhiteSpace(node.TextContent);
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,7 @@ namespace Microsoft.Blazor.Test
|
||||||
// Act
|
// Act
|
||||||
builder.OpenElement("myelement"); // 0: <myelement
|
builder.OpenElement("myelement"); // 0: <myelement
|
||||||
builder.AddAttribute("attribute1", "value 1"); // 1: attribute1="value 1"
|
builder.AddAttribute("attribute1", "value 1"); // 1: attribute1="value 1"
|
||||||
builder.AddAttribute("attribute2", "value 2"); // 2: attribute2="value 2">
|
builder.AddAttribute("attribute2", 123); // 2: attribute2=intExpression123>
|
||||||
builder.OpenElement("child"); // 3: <child
|
builder.OpenElement("child"); // 3: <child
|
||||||
builder.AddAttribute("childevent", eventHandler); // 4: childevent=eventHandler>
|
builder.AddAttribute("childevent", eventHandler); // 4: childevent=eventHandler>
|
||||||
builder.AddText("some text"); // 5: some text
|
builder.AddText("some text"); // 5: some text
|
||||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.Blazor.Test
|
||||||
Assert.Collection(builder.GetNodes(),
|
Assert.Collection(builder.GetNodes(),
|
||||||
node => AssertNode.Element(node, "myelement", 5),
|
node => AssertNode.Element(node, "myelement", 5),
|
||||||
node => AssertNode.Attribute(node, "attribute1", "value 1"),
|
node => AssertNode.Attribute(node, "attribute1", "value 1"),
|
||||||
node => AssertNode.Attribute(node, "attribute2", "value 2"),
|
node => AssertNode.Attribute(node, "attribute2", "123"),
|
||||||
node => AssertNode.Element(node, "child", 5),
|
node => AssertNode.Element(node, "child", 5),
|
||||||
node => AssertNode.Attribute(node, "childevent", eventHandler),
|
node => AssertNode.Attribute(node, "childevent", eventHandler),
|
||||||
node => AssertNode.Text(node, "some text"));
|
node => AssertNode.Text(node, "some text"));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue