Enable Component parameter delegates to not require @() at design time.
- The core issue is that the Razor parser splits attribute values based on whitespace. Therefore, when it encounters `@onclick="() => Foo()"` it breaks it into three different tokens based on the spaces involved. This separation results in multiple adjacent classified spans for C# which is currently unsupported by WTE due to multiple seams overlapping. All that being said we have the opportunity to be smarter when generating attribute values that we feel can be simplified or collapsed; because of this in this PR I changed the `TagHelperBlockRewriter` phase to understand "simple" collapsible blocks and to then collapse them. In the future a goal would be to take a collapsing approach to all potential attributes and then to re-inspect each token individually at higher layers in order to decouple our TagHelper phases from what the parser initially parses.
- Added an integration and parser test to validate the new functionality. Most of the testing is from the fact that no other tests had to change because of this (it doesn't break anything).
- Added a new SyntaxNode method `GetTokens` that flattens a node into only its token representation.
aspnet/AspNetCoredotnet/aspnetcore-tooling#11826
\n\nCommit migrated from 80f1bc76a4
This commit is contained in:
parent
56a440ec9d
commit
88a002a918
|
|
@ -521,6 +521,22 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
_tryParseResult = result;
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitGenericBlock(GenericBlockSyntax node)
|
||||
{
|
||||
if (_tryParseResult.IsBoundNonStringAttribute && CanBeCollapsed(node))
|
||||
{
|
||||
var tokens = node.GetTokens();
|
||||
var expression = SyntaxFactory.CSharpExpressionLiteral(tokens);
|
||||
var rewrittenExpression = (CSharpExpressionLiteralSyntax)VisitCSharpExpressionLiteral(expression);
|
||||
var newChildren = SyntaxListBuilder<RazorSyntaxNode>.Create();
|
||||
newChildren.Add(rewrittenExpression);
|
||||
|
||||
return node.Update(newChildren);
|
||||
}
|
||||
|
||||
return base.VisitGenericBlock(node);
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitCSharpTransition(CSharpTransitionSyntax node)
|
||||
{
|
||||
if (!_tryParseResult.IsBoundNonStringAttribute)
|
||||
|
|
@ -769,6 +785,32 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
return value.WithSpanContext(node.GetSpanContext());
|
||||
}
|
||||
|
||||
// Being collapsed represents that a block contains several identical looking markup literal attribute values. This can be the case
|
||||
// when a user has written something like: @onclick="() => SomeMethod()"
|
||||
// In that case there would be 3 children:
|
||||
// - ()
|
||||
// - =>
|
||||
// - SomeMethod()
|
||||
// There are 3 children because the Razor parser separates attribute values based on whitespace.
|
||||
private static bool CanBeCollapsed(GenericBlockSyntax node)
|
||||
{
|
||||
if (node.Children.Count <= 1)
|
||||
{
|
||||
// The node is either already collapsed or has no children.
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < node.Children.Count; i++)
|
||||
{
|
||||
if (node.Children[i].Kind != SyntaxKind.MarkupLiteralAttributeValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private SyntaxNode ConfigureNonStringAttribute(SyntaxNode node)
|
||||
{
|
||||
var context = node.GetSpanContext();
|
||||
|
|
|
|||
|
|
@ -224,6 +224,35 @@ namespace Microsoft.AspNetCore.Razor.Language.Syntax
|
|||
return ((SyntaxToken)GetFirstTerminal());
|
||||
}
|
||||
|
||||
internal SyntaxList<SyntaxToken> GetTokens()
|
||||
{
|
||||
var tokens = SyntaxListBuilder<SyntaxToken>.Create();
|
||||
|
||||
AddTokens(this, tokens);
|
||||
|
||||
return tokens;
|
||||
|
||||
static void AddTokens(SyntaxNode current, SyntaxListBuilder<SyntaxToken> tokens)
|
||||
{
|
||||
if (current.SlotCount == 0 && current is SyntaxToken token)
|
||||
{
|
||||
// Token
|
||||
tokens.Add(token);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < current.SlotCount; i++)
|
||||
{
|
||||
var child = current.GetNodeSlot(i);
|
||||
|
||||
if (child != null)
|
||||
{
|
||||
AddTokens(child, tokens);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal SyntaxToken GetLastToken()
|
||||
{
|
||||
return ((SyntaxToken)GetLastTerminal());
|
||||
|
|
|
|||
|
|
@ -2352,6 +2352,39 @@ namespace Test
|
|||
|
||||
#region Event Handlers
|
||||
|
||||
[Fact]
|
||||
public void Component_WithImplicitLambdaEventHandler()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : ComponentBase
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
// Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<MyComponent @onclick=""() => Increment()""/>
|
||||
|
||||
@code {
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
}");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChildComponent_WithLambdaEventHandler()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -423,6 +423,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
|||
.Build()
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void UnderstandsMultipartNonStringTagHelperAttributes()
|
||||
{
|
||||
EvaluateData(CodeTagHelperAttributes_Descriptors, "<person age=\"(() => 123)()\" />");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CreatesMarkupCodeSpansForNonStringTagHelperAttributes1()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase
|
||||
{
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
}
|
||||
#pragma warning restore 219
|
||||
#pragma warning disable 0414
|
||||
private static System.Object __o = null;
|
||||
#pragma warning restore 0414
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder)
|
||||
{
|
||||
__o = Microsoft.AspNetCore.Components.EventCallback.Factory.Create<Microsoft.AspNetCore.Components.UIMouseEventArgs>(this,
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
() => Increment()
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
);
|
||||
builder.AddAttribute(-1, "ChildContent", (Microsoft.AspNetCore.Components.RenderFragment)((builder2) => {
|
||||
}
|
||||
));
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
__o = typeof(MyComponent);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#nullable restore
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [12] ) - System
|
||||
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||
UsingDirective - (104:5,1 [37] ) - Microsoft.AspNetCore.Components
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
|
||||
DesignTimeDirective -
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
Component - (0:0,0 [43] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
|
||||
ComponentAttribute - (23:0,23 [17] x:\dir\subdir\Test\TestComponent.cshtml) - onclick - AttributeStructure.DoubleQuotes
|
||||
CSharpExpression -
|
||||
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.Create<Microsoft.AspNetCore.Components.UIMouseEventArgs>(this,
|
||||
IntermediateToken - (23:0,23 [17] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - () => Increment()
|
||||
IntermediateToken - - CSharp - )
|
||||
HtmlContent - (43:0,43 [4] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (43:0,43 [4] x:\dir\subdir\Test\TestComponent.cshtml) - Html - \n\n
|
||||
CSharpCode - (54:2,7 [87] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (54:2,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private int counter;\n private void Increment() {\n counter++;\n }\n
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
Source Location: (23:0,23 [17] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|() => Increment()|
|
||||
Generated Location: (1002:25,23 [17] )
|
||||
|() => Increment()|
|
||||
|
||||
Source Location: (54:2,7 [87] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
|
|
||||
Generated Location: (1512:45,7 [87] )
|
||||
|
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
public class TestComponent : Microsoft.AspNetCore.Components.ComponentBase
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder builder)
|
||||
{
|
||||
builder.OpenComponent<Test.MyComponent>(0);
|
||||
builder.AddAttribute(1, "onclick", Microsoft.AspNetCore.Components.EventCallback.Factory.Create<Microsoft.AspNetCore.Components.UIMouseEventArgs>(this,
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
() => Increment()
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
));
|
||||
builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
#nullable restore
|
||||
#line 3 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
|
||||
ClassDeclaration - - public - TestComponent - Microsoft.AspNetCore.Components.ComponentBase -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
Component - (0:0,0 [43] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
|
||||
ComponentAttribute - (23:0,23 [17] x:\dir\subdir\Test\TestComponent.cshtml) - onclick - AttributeStructure.DoubleQuotes
|
||||
CSharpExpression -
|
||||
IntermediateToken - - CSharp - Microsoft.AspNetCore.Components.EventCallback.Factory.Create<Microsoft.AspNetCore.Components.UIMouseEventArgs>(this,
|
||||
IntermediateToken - (23:0,23 [17] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - () => Increment()
|
||||
IntermediateToken - - CSharp - )
|
||||
CSharpCode - (54:2,7 [87] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
IntermediateToken - (54:2,7 [87] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - \n private int counter;\n private void Increment() {\n counter++;\n }\n
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
Source Location: (54:2,7 [87] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
|
|
||||
Generated Location: (1071:30,7 [87] )
|
||||
|
|
||||
private int counter;
|
||||
private void Increment() {
|
||||
counter++;
|
||||
}
|
||||
|
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
Code span at (13:0,13 [13] ) (Accepts:AnyExceptNewline) - Parent: Tag block at (0:0,0 [30] )
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
RazorDocument - [0..30)::30 - [<person age="(() => 123)()" />]
|
||||
MarkupBlock - [0..30)::30
|
||||
MarkupTagHelperElement - [0..30)::30 - person[SelfClosing] - PersonTagHelper
|
||||
MarkupTagHelperStartTag - [0..30)::30 - [<person age="(() => 123)()" />] - Gen<Markup> - SpanEditHandler;Accepts:Any
|
||||
OpenAngle;[<];
|
||||
Text;[person];
|
||||
MarkupTagHelperAttribute - [7..27)::20 - age - DoubleQuotes - Bound - [ age="(() => 123)()"]
|
||||
MarkupTextLiteral - [7..8)::1 - [ ] - Gen<Markup> - SpanEditHandler;Accepts:Any
|
||||
Whitespace;[ ];
|
||||
MarkupTextLiteral - [8..11)::3 - [age] - Gen<Markup> - SpanEditHandler;Accepts:Any
|
||||
Text;[age];
|
||||
Equals;[=];
|
||||
MarkupTextLiteral - [12..13)::1 - ["] - Gen<None> - SpanEditHandler;Accepts:Any
|
||||
DoubleQuote;["];
|
||||
MarkupTagHelperAttributeValue - [13..26)::13
|
||||
CSharpExpressionLiteral - [13..26)::13 - [(() => 123)()] - Gen<None> - ImplicitExpressionEditHandler;Accepts:AnyExceptNewline;ImplicitExpression[ATD];K14
|
||||
Text;[(()];
|
||||
Whitespace;[ ];
|
||||
Equals;[=];
|
||||
CloseAngle;[>];
|
||||
Whitespace;[ ];
|
||||
Text;[123)()];
|
||||
MarkupTextLiteral - [26..27)::1 - ["] - Gen<None> - SpanEditHandler;Accepts:Any
|
||||
DoubleQuote;["];
|
||||
MarkupMiscAttributeContent - [27..28)::1
|
||||
MarkupTextLiteral - [27..28)::1 - [ ] - Gen<Markup> - SpanEditHandler;Accepts:Any
|
||||
Whitespace;[ ];
|
||||
ForwardSlash;[/];
|
||||
CloseAngle;[>];
|
||||
|
|
@ -0,0 +1 @@
|
|||
TagHelper span at (0:0,0 [30] ) - PersonTagHelper
|
||||
Loading…
Reference in New Issue