Avoid component syntax confusion by making it a compile-time error. Fixes #145
This commit is contained in:
parent
1a31634b70
commit
08d7b77d38
|
|
@ -246,6 +246,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
.WriteEndMethodInvocation();
|
.WriteEndMethodInvocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isComponent && nextTag.Attributes.Count > 0)
|
||||||
|
{
|
||||||
|
ThrowTemporaryComponentSyntaxError(node, nextTag, tagNameOriginalCase);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var attribute in nextTag.Attributes)
|
foreach (var attribute in nextTag.Attributes)
|
||||||
{
|
{
|
||||||
WriteAttribute(codeWriter, attribute.Key, attribute.Value);
|
WriteAttribute(codeWriter, attribute.Key, attribute.Value);
|
||||||
|
|
@ -316,6 +321,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ThrowTemporaryComponentSyntaxError(HtmlContentIntermediateNode node, HtmlTagToken tag, string componentName)
|
||||||
|
=> throw new RazorCompilerException(
|
||||||
|
$"Wrong syntax for '{tag.Attributes[0].Key}' on '{componentName}': As a temporary " +
|
||||||
|
$"limitation, component attributes must be expressed with C# syntax. For example, " +
|
||||||
|
$"SomeParam=@(\"Some value\") is allowed, but SomeParam=\"Some value\" is not.",
|
||||||
|
CalculateSourcePosition(node.Source, tag.Position));
|
||||||
|
|
||||||
private SourceSpan? CalculateSourcePosition(
|
private SourceSpan? CalculateSourcePosition(
|
||||||
SourceSpan? razorTokenPosition,
|
SourceSpan? razorTokenPosition,
|
||||||
TextPosition htmlNodePosition)
|
TextPosition htmlNodePosition)
|
||||||
|
|
|
||||||
|
|
@ -381,40 +381,71 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var testComponentTypeName = typeof(TestComponent).FullName.Replace('+', '.');
|
var testComponentTypeName = typeof(TestComponent).FullName.Replace('+', '.');
|
||||||
var testObjectTypeName = typeof(SomeType).FullName.Replace('+', '.');
|
var testObjectTypeName = typeof(SomeType).FullName.Replace('+', '.');
|
||||||
|
// TODO: Once we have the improved component tooling and can allow syntax
|
||||||
|
// like StringProperty="My string" or BoolProperty=true, update this
|
||||||
|
// test to use that syntax.
|
||||||
var component = CompileToComponent($"<c:{testComponentTypeName}" +
|
var component = CompileToComponent($"<c:{testComponentTypeName}" +
|
||||||
$" IntProperty=123" +
|
$" IntProperty=@(123)" +
|
||||||
$" StringProperty=\"My string\"" +
|
$" BoolProperty=@true" +
|
||||||
|
$" StringProperty=@(\"My string\")" +
|
||||||
$" ObjectProperty=@(new {testObjectTypeName}()) />");
|
$" ObjectProperty=@(new {testObjectTypeName}()) />");
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
// TODO: Fix this.
|
|
||||||
// * Currently the attribute names are lowercased if they were
|
|
||||||
// parsed by AngleSharp as HTML, and left in their original case if they
|
|
||||||
// were parsed by the Razor compiler as a C# expression. They should all
|
|
||||||
// retain their original case when the target element represents a component.
|
|
||||||
// * Similarly, unquoted values are interpreted as strings if they were parsed
|
|
||||||
// by AngleSharp (e.g., intproperty=123 passes a string). The values should
|
|
||||||
// always be treated as C# expressions if the target represents a component.
|
|
||||||
// This problem will probably go away on its own when we have new component
|
|
||||||
// tooling.
|
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 4, 0),
|
frame => AssertFrame.Component<TestComponent>(frame, 5, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "intproperty", "123", 1),
|
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
||||||
frame => AssertFrame.Attribute(frame, "stringproperty", "My string", 2),
|
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
||||||
|
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "ObjectProperty");
|
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
||||||
Assert.IsType<SomeType>(frame.AttributeValue);
|
Assert.IsType<SomeType>(frame.AttributeValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TemporaryComponentSyntaxRejectsParametersExpressedAsPlainHtmlAttributes()
|
||||||
|
{
|
||||||
|
// This is a temporary syntax restriction. Currently you can write:
|
||||||
|
// <c:MyComponent MyParam=@("My value") />
|
||||||
|
// ... but are *not* allowed to write:
|
||||||
|
// <c:MyComponent MyParam="My value" />
|
||||||
|
// This is because until we get the improved taghelper-based tooling,
|
||||||
|
// we're using AngleSharp to parse the plain HTML attributes, and it
|
||||||
|
// suffers from limitations:
|
||||||
|
// * Loses the casing of attribute names (MyParam becomes myparam)
|
||||||
|
// * Doesn't recognize MyBool=true as an bool (becomes mybool="true"),
|
||||||
|
// plus equivalent for other primitives like enum values
|
||||||
|
// So to avoid people getting runtime errors, we're currently imposing
|
||||||
|
// the compile-time restriction that component params have to be given
|
||||||
|
// as C# expressions, e.g., MyBool=@true and MyString=@("Hello")
|
||||||
|
|
||||||
|
// Arrange/Act
|
||||||
|
var result = CompileToCSharp(
|
||||||
|
$"Line 1\n" +
|
||||||
|
$"Some text <c:MyComponent MyParam=\"My value\" />");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(result.Diagnostics,
|
||||||
|
item =>
|
||||||
|
{
|
||||||
|
Assert.Equal(RazorCompilerDiagnostic.DiagnosticType.Error, item.Type);
|
||||||
|
Assert.StartsWith($"Wrong syntax for 'myparam' on 'c:MyComponent': As a temporary " +
|
||||||
|
$"limitation, component attributes must be expressed with C# syntax. For " +
|
||||||
|
$"example, SomeParam=@(\"Some value\") is allowed, but SomeParam=\"Some value\" " +
|
||||||
|
$"is not.", item.Message);
|
||||||
|
Assert.Equal(2, item.Line);
|
||||||
|
Assert.Equal(11, item.Column);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanIncludeChildrenInComponents()
|
public void CanIncludeChildrenInComponents()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange/Act
|
||||||
var testComponentTypeName = typeof(TestComponent).FullName.Replace('+', '.');
|
var testComponentTypeName = typeof(TestComponent).FullName.Replace('+', '.');
|
||||||
var component = CompileToComponent($"<c:{testComponentTypeName} attr=\"abc\">" +
|
var component = CompileToComponent($"<c:{testComponentTypeName} MyAttr=@(\"abc\")>" +
|
||||||
$"Some text" +
|
$"Some text" +
|
||||||
$"<some-child a='1'>Nested text</some-child>" +
|
$"<some-child a='1'>Nested text</some-child>" +
|
||||||
$"</c:{testComponentTypeName}>");
|
$"</c:{testComponentTypeName}>");
|
||||||
|
|
@ -423,7 +454,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
// Assert: component frames are correct
|
// Assert: component frames are correct
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 3, 0),
|
frame => AssertFrame.Component<TestComponent>(frame, 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "attr", "abc", 1),
|
frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
|
||||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2));
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2));
|
||||||
|
|
||||||
// Assert: Captured ChildContent frames are correct
|
// Assert: Captured ChildContent frames are correct
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue