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);
|
||||
|
||||
private string _unconsumedHtml;
|
||||
private string _currentAttributeName;
|
||||
private IList<object> _currentAttributeValues;
|
||||
private IDictionary<string, object> _currentElementAttributes = new Dictionary<string, object>();
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
_currentAttributeName = node.AttributeName;
|
||||
_currentAttributeValues = new List<object>();
|
||||
context.RenderChildren(node);
|
||||
_currentAttributeName = null;
|
||||
_currentElementAttributes[node.AttributeName] = _currentAttributeValues;
|
||||
_currentAttributeValues = null;
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
@ -208,35 +224,11 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
|
||||
private static void WriteAttribute(CodeWriter codeWriter, string key, object value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
codeWriter
|
||||
.WriteStartMethodInvocation($"{builderVarName}.{nameof(RenderTreeBuilder.AddAttribute)}")
|
||||
.WriteStringLiteral(key)
|
||||
.WriteParameterSeparator();
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
WriteAttributeValue(codeWriter, value);
|
||||
codeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
|
|
@ -255,5 +247,52 @@ namespace Microsoft.Blazor.Build.Core.RazorCompilation.Engine
|
|||
}
|
||||
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));
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Appends a node representing a child component.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -184,6 +184,34 @@ namespace Microsoft.Blazor.Build.Test
|
|||
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)
|
||||
=> node.NodeType != RenderTreeNodeType.Text
|
||||
|| !string.IsNullOrWhiteSpace(node.TextContent);
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ namespace Microsoft.Blazor.Test
|
|||
// Act
|
||||
builder.OpenElement("myelement"); // 0: <myelement
|
||||
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.AddAttribute("childevent", eventHandler); // 4: childevent=eventHandler>
|
||||
builder.AddText("some text"); // 5: some text
|
||||
|
|
@ -164,7 +164,7 @@ namespace Microsoft.Blazor.Test
|
|||
Assert.Collection(builder.GetNodes(),
|
||||
node => AssertNode.Element(node, "myelement", 5),
|
||||
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.Attribute(node, "childevent", eventHandler),
|
||||
node => AssertNode.Text(node, "some text"));
|
||||
|
|
|
|||
Loading…
Reference in New Issue