Implement components as tag helpers
Implements Component code generation and tooling support end to end udditionally adds some default `@addTagHelper` directives to make programming in Blazor a little nicer. Components are discovered as Tag Helpers using Razor's extensibility during the build/IDE process. This drives the code generation during build and lights up a bunch of editor features. Add
This commit is contained in:
parent
b47e3095ee
commit
601e7914f7
|
|
@ -39,7 +39,7 @@
|
||||||
</RazorExtension>
|
</RazorExtension>
|
||||||
|
|
||||||
<!-- Path used for the temporary compilation we produce for component discovery -->
|
<!-- Path used for the temporary compilation we produce for component discovery -->
|
||||||
<_BlazorTempAssembly Include="$(IntermediateOutputPath)$(TargetName).BlazorTemp.dll" />
|
<_BlazorTempAssembly Include="$(IntermediateOutputPath)BlazorTemp\$(TargetName).dll" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
@ -294,6 +294,8 @@
|
||||||
Outputs="@(_BlazorTempAssembly);$(NonExistentFile)"
|
Outputs="@(_BlazorTempAssembly);$(NonExistentFile)"
|
||||||
Condition="'$(DesignTimeBuild)'!='true'">
|
Condition="'$(DesignTimeBuild)'!='true'">
|
||||||
|
|
||||||
|
<MakeDir Directories="%(_BlazorTempAssembly.RelativeDir)" />
|
||||||
|
|
||||||
<!-- These two compiler warnings are raised when a reference is bound to a different version
|
<!-- These two compiler warnings are raised when a reference is bound to a different version
|
||||||
than specified in the assembly reference version number. MSBuild raises the same warning in this case,
|
than specified in the assembly reference version number. MSBuild raises the same warning in this case,
|
||||||
so the compiler warning would be redundant. -->
|
so the compiler warning would be redundant. -->
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
public static readonly string OpenComponent = nameof(OpenComponent);
|
public static readonly string OpenComponent = nameof(OpenComponent);
|
||||||
|
|
||||||
public static readonly string CloseComponent = nameof(CloseElement);
|
public static readonly string CloseComponent = nameof(CloseComponent);
|
||||||
|
|
||||||
public static readonly string AddContent = nameof(AddContent);
|
public static readonly string AddContent = nameof(AddContent);
|
||||||
|
|
||||||
|
|
@ -65,5 +65,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
public static readonly string SetValue = "Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValue";
|
public static readonly string SetValue = "Microsoft.AspNetCore.Blazor.Components.BindMethods.SetValue";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class UIEventHandler
|
||||||
|
{
|
||||||
|
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.UIEventHandler";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Directs a <see cref="DocumentWriter"/> to use <see cref="BlazorIntermediateNodeWriter"/>.
|
/// Directs a <see cref="DocumentWriter"/> to use <see cref="BlazorRuntimeNodeWriter"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class BlazorCodeTarget : CodeTarget
|
internal class BlazorCodeTarget : CodeTarget
|
||||||
{
|
{
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
public override IntermediateNodeWriter CreateNodeWriter()
|
public override IntermediateNodeWriter CreateNodeWriter()
|
||||||
{
|
{
|
||||||
return _options.DesignTime ? (IntermediateNodeWriter)new DesignTimeNodeWriter() : new BlazorIntermediateNodeWriter();
|
return _options.DesignTime ? (BlazorNodeWriter)new BlazorDesignTimeNodeWriter() : new BlazorRuntimeNodeWriter();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override TExtension GetExtension<TExtension>()
|
public override TExtension GetExtension<TExtension>()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,499 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
// Based on the DesignTimeNodeWriter from Razor repo.
|
||||||
|
internal class BlazorDesignTimeNodeWriter : BlazorNodeWriter
|
||||||
|
{
|
||||||
|
private readonly ScopeStack _scopeStack = new ScopeStack();
|
||||||
|
|
||||||
|
private readonly static string DesignTimeVariable = "__o";
|
||||||
|
|
||||||
|
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Source.HasValue)
|
||||||
|
{
|
||||||
|
using (context.CodeWriter.BuildLinePragma(node.Source.Value))
|
||||||
|
{
|
||||||
|
context.AddSourceMappingFor(node);
|
||||||
|
context.CodeWriter.WriteUsing(node.Content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.WriteUsing(node.Content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Children.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Source != null)
|
||||||
|
{
|
||||||
|
using (context.CodeWriter.BuildLinePragma(node.Source.Value))
|
||||||
|
{
|
||||||
|
var offset = DesignTimeVariable.Length + " = ".Length;
|
||||||
|
context.CodeWriter.WritePadding(offset, node.Source, context);
|
||||||
|
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||||
|
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
context.AddSourceMappingFor(token);
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There may be something else inside the expression like a Template or another extension node.
|
||||||
|
context.RenderNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.WriteLine(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There may be something else inside the expression like a Template or another extension node.
|
||||||
|
context.RenderNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.CodeWriter.WriteLine(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
var isWhitespaceStatement = true;
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
var token = node.Children[i] as IntermediateToken;
|
||||||
|
if (token == null || !string.IsNullOrWhiteSpace(token.Content))
|
||||||
|
{
|
||||||
|
isWhitespaceStatement = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IDisposable linePragmaScope = null;
|
||||||
|
if (node.Source != null)
|
||||||
|
{
|
||||||
|
if (!isWhitespaceStatement)
|
||||||
|
{
|
||||||
|
linePragmaScope = context.CodeWriter.BuildLinePragma(node.Source.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.WritePadding(0, node.Source.Value, context);
|
||||||
|
}
|
||||||
|
else if (isWhitespaceStatement)
|
||||||
|
{
|
||||||
|
// Don't write whitespace if there is no line mapping for it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
context.AddSourceMappingFor(token);
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There may be something else inside the statement like an extension node.
|
||||||
|
context.RenderNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linePragmaScope != null)
|
||||||
|
{
|
||||||
|
linePragmaScope.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.RenderChildren(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteHtmlAttributeValue(CodeRenderingContext context, HtmlAttributeValueIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
context.RenderChildren(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteCSharpExpressionAttributeValue(CodeRenderingContext context, CSharpExpressionAttributeValueIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Children.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstChild = node.Children[0];
|
||||||
|
if (firstChild.Source != null)
|
||||||
|
{
|
||||||
|
using (context.CodeWriter.BuildLinePragma(firstChild.Source.Value))
|
||||||
|
{
|
||||||
|
var offset = DesignTimeVariable.Length + " = ".Length;
|
||||||
|
context.CodeWriter.WritePadding(offset, firstChild.Source, context);
|
||||||
|
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||||
|
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
context.AddSourceMappingFor(token);
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There may be something else inside the expression like a Template or another extension node.
|
||||||
|
context.RenderNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.WriteLine(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.WriteStartAssignment(DesignTimeVariable);
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
if (token.Source != null)
|
||||||
|
{
|
||||||
|
context.AddSourceMappingFor(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There may be something else inside the expression like a Template or another extension node.
|
||||||
|
context.RenderNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
context.CodeWriter.WriteLine(";");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < node.Children.Count; i++)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
IDisposable linePragmaScope = null;
|
||||||
|
var isWhitespaceStatement = string.IsNullOrWhiteSpace(token.Content);
|
||||||
|
|
||||||
|
if (token.Source != null)
|
||||||
|
{
|
||||||
|
if (!isWhitespaceStatement)
|
||||||
|
{
|
||||||
|
linePragmaScope = context.CodeWriter.BuildLinePragma(token.Source.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.WritePadding(0, token.Source.Value, context);
|
||||||
|
}
|
||||||
|
else if (isWhitespaceStatement)
|
||||||
|
{
|
||||||
|
// Don't write whitespace if there is no line mapping for it.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.AddSourceMappingFor(token);
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
|
||||||
|
if (linePragmaScope != null)
|
||||||
|
{
|
||||||
|
linePragmaScope.Dispose();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// There may be something else inside the statement like an extension node.
|
||||||
|
context.RenderNode(node.Children[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteHtmlContent(CodeRenderingContext context, HtmlContentIntermediateNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
||||||
|
{
|
||||||
|
codeWriter
|
||||||
|
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(BlazorApi.RenderTreeBuilder.AddAttribute)}")
|
||||||
|
.Write("-1")
|
||||||
|
.WriteParameterSeparator()
|
||||||
|
.WriteStringLiteral(key)
|
||||||
|
.WriteParameterSeparator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentOpen(CodeRenderingContext context, ComponentOpenExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentClose(CodeRenderingContext context, ComponentCloseExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentBody(CodeRenderingContext context, ComponentBodyExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to be aware of the blazor scope-tracking concept in design-time code generation
|
||||||
|
// because each component creates a lambda scope for its child content.
|
||||||
|
//
|
||||||
|
// We're hacking it a bit here by just forcing every component to have an empty lambda
|
||||||
|
_scopeStack.OpenScope(node.TagName, isComponent: true);
|
||||||
|
_scopeStack.IncrementCurrentScopeChildCount(context);
|
||||||
|
context.RenderChildren(node);
|
||||||
|
_scopeStack.CloseScope(context, node.TagName, isComponent: true, source: node.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// For design time we only care about the case where the attribute has c# code.
|
||||||
|
//
|
||||||
|
// We also limit component attributes to simple cases. However there is still a lot of complexity
|
||||||
|
// to handle here, since there are a few different cases for how an attribute might be structured.
|
||||||
|
//
|
||||||
|
// This rougly follows the design of the runtime writer for simplicity.
|
||||||
|
if (node.AttributeStructure == AttributeStructure.Minimized)
|
||||||
|
{
|
||||||
|
// Do nothhing
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
node.Children.Count != 1 ||
|
||||||
|
node.Children[0] is HtmlContentIntermediateNode htmlNode && htmlNode.Children.Count != 1 ||
|
||||||
|
node.Children[0] is CSharpExpressionIntermediateNode cSharpNode && cSharpNode.Children.Count != 1)
|
||||||
|
{
|
||||||
|
// We don't expect this to happen, we just want to know if it can.
|
||||||
|
throw new InvalidOperationException("Attribute nodes should either be minimized or a single content node.");
|
||||||
|
}
|
||||||
|
else if (node.BoundAttribute.IsUIEventHandlerProperty())
|
||||||
|
{
|
||||||
|
// See the runtime version of this code for a thorough description of what we're doing here
|
||||||
|
if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null)
|
||||||
|
{
|
||||||
|
// This is an escaped event handler
|
||||||
|
context.CodeWriter.Write(DesignTimeVariable);
|
||||||
|
context.CodeWriter.Write(" = ");
|
||||||
|
context.CodeWriter.Write("new ");
|
||||||
|
context.CodeWriter.Write(node.BoundAttribute.TypeName);
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
WriteCSharpToken(context, ((IntermediateToken)cSharpNode.Children[0]));
|
||||||
|
context.CodeWriter.Write(");");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(DesignTimeVariable);
|
||||||
|
context.CodeWriter.Write(" = ");
|
||||||
|
context.CodeWriter.Write("new ");
|
||||||
|
context.CodeWriter.Write(node.BoundAttribute.TypeName);
|
||||||
|
context.CodeWriter.Write("(e => ");
|
||||||
|
WriteCSharpToken(context, ((IntermediateToken)node.Children[0]));
|
||||||
|
context.CodeWriter.Write(");");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null)
|
||||||
|
{
|
||||||
|
// This is the case when an attribute has an explicit C# transition like:
|
||||||
|
// <MyComponent Foo="@bar" />
|
||||||
|
context.CodeWriter.Write(DesignTimeVariable);
|
||||||
|
context.CodeWriter.Write(" = ");
|
||||||
|
WriteCSharpToken(context, ((IntermediateToken)cSharpNode.Children[0]));
|
||||||
|
context.CodeWriter.Write(";");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
else if ((htmlNode = node.Children[0] as HtmlContentIntermediateNode) != null)
|
||||||
|
{
|
||||||
|
// Do nothing
|
||||||
|
}
|
||||||
|
else if (node.Children[0] is IntermediateToken token && token.IsCSharp)
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(DesignTimeVariable);
|
||||||
|
context.CodeWriter.Write(" = ");
|
||||||
|
WriteCSharpToken(context, token);
|
||||||
|
context.CodeWriter.Write(";");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteCSharpToken(CodeRenderingContext context, IntermediateToken token)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(token.Content))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token.Source?.FilePath == null)
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
using (context.CodeWriter.BuildLinePragma(token.Source))
|
||||||
|
{
|
||||||
|
context.CodeWriter.WritePadding(0, token.Source.Value, context);
|
||||||
|
context.AddSourceMappingFor(token);
|
||||||
|
context.CodeWriter.Write(token.Content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// 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.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
using AngleSharp;
|
using AngleSharp;
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
{
|
{
|
||||||
|
|
@ -51,6 +54,40 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
return RazorDiagnostic.Create(MismatchedClosingTagKind, span ?? SourceSpan.Undefined, tagName, kind, expectedKind);
|
return RazorDiagnostic.Create(MismatchedClosingTagKind, span ?? SourceSpan.Undefined, tagName, kind, expectedKind);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly RazorDiagnosticDescriptor MultipleComponents = new RazorDiagnosticDescriptor(
|
||||||
|
"BL9985",
|
||||||
|
() => "Multiple components use the tag '{0}'. Components: {1}",
|
||||||
|
RazorDiagnosticSeverity.Error);
|
||||||
|
|
||||||
|
public static RazorDiagnostic Create_MultipleComponents(SourceSpan? span, string tagName, IEnumerable<TagHelperDescriptor> components)
|
||||||
|
{
|
||||||
|
return RazorDiagnostic.Create(MultipleComponents, span ?? SourceSpan.Undefined, tagName, string.Join(", ", components.Select(c => c.DisplayName)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly RazorDiagnosticDescriptor UnsupportedComplexContent = new RazorDiagnosticDescriptor(
|
||||||
|
"BL9986",
|
||||||
|
() => "Component attributes do not support complex content (mixed C# and markup). Attribute: '{0}', text '{1}'",
|
||||||
|
RazorDiagnosticSeverity.Error);
|
||||||
|
|
||||||
|
public static RazorDiagnostic Create_UnsupportedComplexContent(
|
||||||
|
SourceSpan? source,
|
||||||
|
TagHelperPropertyIntermediateNode node,
|
||||||
|
IntermediateNodeCollection children)
|
||||||
|
{
|
||||||
|
var content = string.Join("", children.OfType<IntermediateToken>().Select(t => t.Content));
|
||||||
|
return RazorDiagnostic.Create(UnsupportedComplexContent, source ?? SourceSpan.Undefined, node.AttributeName, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly RazorDiagnosticDescriptor UnboundComponentAttribute = new RazorDiagnosticDescriptor(
|
||||||
|
"BL9987",
|
||||||
|
() => "The component '{0}' does not have an attribute named '{1}'.",
|
||||||
|
RazorDiagnosticSeverity.Error);
|
||||||
|
|
||||||
|
public static RazorDiagnostic Create_UnboundComponentAttribute(SourceSpan? source, string componentType, TagHelperHtmlAttributeIntermediateNode node)
|
||||||
|
{
|
||||||
|
return RazorDiagnostic.Create(UnboundComponentAttribute, source ?? SourceSpan.Undefined, componentType, node.AttributeName);
|
||||||
|
}
|
||||||
|
|
||||||
private static SourceSpan? CalculateSourcePosition(
|
private static SourceSpan? CalculateSourcePosition(
|
||||||
SourceSpan? razorTokenPosition,
|
SourceSpan? razorTokenPosition,
|
||||||
TextPosition htmlNodePosition)
|
TextPosition htmlNodePosition)
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
builder.Features.Add(new ConfigureBlazorCodeGenerationOptions());
|
builder.Features.Add(new ConfigureBlazorCodeGenerationOptions());
|
||||||
|
|
||||||
builder.Features.Add(new ComponentDocumentClassifierPass());
|
builder.Features.Add(new ComponentDocumentClassifierPass());
|
||||||
|
builder.Features.Add(new ComponentLoweringPass());
|
||||||
|
|
||||||
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
{
|
{
|
||||||
private const string ImportsFileName = "_ViewImports.cshtml";
|
private const string ImportsFileName = "_ViewImports.cshtml";
|
||||||
|
|
||||||
public RazorProjectItem DefaultImports => VirtualProjectItem.Instance;
|
private const string DefaultUsingImportContent = @"
|
||||||
|
@using System
|
||||||
|
@using System.Collections.Generic
|
||||||
|
@using System.Linq
|
||||||
|
@using System.Threading.Tasks
|
||||||
|
";
|
||||||
|
|
||||||
public RazorProjectEngine ProjectEngine { get; set; }
|
public RazorProjectEngine ProjectEngine { get; set; }
|
||||||
|
|
||||||
|
|
@ -27,9 +32,23 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
var imports = new List<RazorProjectItem>()
|
var imports = new List<RazorProjectItem>()
|
||||||
{
|
{
|
||||||
VirtualProjectItem.Instance,
|
new VirtualProjectItem(DefaultUsingImportContent),
|
||||||
|
new VirtualProjectItem(@"@addTagHelper ""*, Microsoft.AspNetCore.Blazor"""),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Try and infer a namespace from the project directory. We don't yet have the ability to pass
|
||||||
|
// the namespace through from the project.
|
||||||
|
if (projectItem.PhysicalPath != null && projectItem.FilePath != null)
|
||||||
|
{
|
||||||
|
var trimLength = projectItem.FilePath.Length + (projectItem.FilePath.StartsWith("/") ? 0 : 1);
|
||||||
|
var baseDirectory = projectItem.PhysicalPath.Substring(0, projectItem.PhysicalPath.Length - trimLength);
|
||||||
|
var baseNamespace = Path.GetFileName(baseDirectory);
|
||||||
|
if (!string.IsNullOrEmpty(baseNamespace))
|
||||||
|
{
|
||||||
|
imports.Add(new VirtualProjectItem($@"@addTagHelper ""*, {baseNamespace}"""));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We add hierarchical imports second so any default directive imports can be overridden.
|
// We add hierarchical imports second so any default directive imports can be overridden.
|
||||||
imports.AddRange(GetHierarchicalImports(ProjectEngine.FileSystem, projectItem));
|
imports.AddRange(GetHierarchicalImports(ProjectEngine.FileSystem, projectItem));
|
||||||
|
|
||||||
|
|
@ -45,22 +64,16 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
private class VirtualProjectItem : RazorProjectItem
|
private class VirtualProjectItem : RazorProjectItem
|
||||||
{
|
{
|
||||||
private readonly byte[] _defaultImportBytes;
|
private readonly byte[] _bytes;
|
||||||
|
|
||||||
private VirtualProjectItem()
|
public VirtualProjectItem(string content)
|
||||||
{
|
{
|
||||||
var preamble = Encoding.UTF8.GetPreamble();
|
var preamble = Encoding.UTF8.GetPreamble();
|
||||||
var content = @"
|
|
||||||
@using System
|
|
||||||
@using System.Collections.Generic
|
|
||||||
@using System.Linq
|
|
||||||
@using System.Threading.Tasks
|
|
||||||
";
|
|
||||||
var contentBytes = Encoding.UTF8.GetBytes(content);
|
var contentBytes = Encoding.UTF8.GetBytes(content);
|
||||||
|
|
||||||
_defaultImportBytes = new byte[preamble.Length + contentBytes.Length];
|
_bytes = new byte[preamble.Length + contentBytes.Length];
|
||||||
preamble.CopyTo(_defaultImportBytes, 0);
|
preamble.CopyTo(_bytes, 0);
|
||||||
contentBytes.CopyTo(_defaultImportBytes, preamble.Length);
|
contentBytes.CopyTo(_bytes, preamble.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string BasePath => null;
|
public override string BasePath => null;
|
||||||
|
|
@ -71,9 +84,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
|
||||||
public override bool Exists => true;
|
public override bool Exists => true;
|
||||||
|
|
||||||
public static VirtualProjectItem Instance { get; } = new VirtualProjectItem();
|
public override Stream Read() => new MemoryStream(_bytes);
|
||||||
|
|
||||||
public override Stream Read() => new MemoryStream(_defaultImportBytes);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal abstract class BlazorNodeWriter : IntermediateNodeWriter
|
||||||
|
{
|
||||||
|
public sealed override void BeginWriterScope(CodeRenderingContext context, string writer)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException(nameof(BeginWriterScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed override void EndWriterScope(CodeRenderingContext context)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException(nameof(EndWriterScope));
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void BeginWriteAttribute(CodeWriter codeWriter, string key);
|
||||||
|
|
||||||
|
public abstract void WriteComponentOpen(CodeRenderingContext context, ComponentOpenExtensionNode node);
|
||||||
|
|
||||||
|
public abstract void WriteComponentClose(CodeRenderingContext context, ComponentCloseExtensionNode node);
|
||||||
|
|
||||||
|
public abstract void WriteComponentBody(CodeRenderingContext context, ComponentBodyExtensionNode node);
|
||||||
|
|
||||||
|
public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Generates the C# code corresponding to Razor source document contents.
|
/// Generates the C# code corresponding to Razor source document contents.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal class BlazorIntermediateNodeWriter : IntermediateNodeWriter
|
internal class BlazorRuntimeNodeWriter : BlazorNodeWriter
|
||||||
{
|
{
|
||||||
// Per the HTML spec, the following elements are inherently self-closing
|
// Per the HTML spec, the following elements are inherently self-closing
|
||||||
// For example, <img> is the same as <img /> (and therefore it cannot contain descendants)
|
// For example, <img> is the same as <img /> (and therefore it cannot contain descendants)
|
||||||
|
|
@ -48,16 +48,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
public IntermediateToken AttributeValue;
|
public IntermediateToken AttributeValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void BeginWriterScope(CodeRenderingContext context, string writer)
|
|
||||||
{
|
|
||||||
throw new System.NotImplementedException(nameof(BeginWriterScope));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void EndWriterScope(CodeRenderingContext context)
|
|
||||||
{
|
|
||||||
throw new System.NotImplementedException(nameof(EndWriterScope));
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
public override void WriteCSharpCode(CodeRenderingContext context, CSharpCodeIntermediateNode node)
|
||||||
{
|
{
|
||||||
var isWhitespaceStatement = true;
|
var isWhitespaceStatement = true;
|
||||||
|
|
@ -373,6 +363,173 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||||
|
{
|
||||||
|
context.CodeWriter.WriteUsing(node.Content, endLine: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentOpen(CodeRenderingContext context, ComponentOpenExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The start tag counts as a child from a markup point of view.
|
||||||
|
_scopeStack.IncrementCurrentScopeChildCount(context);
|
||||||
|
|
||||||
|
// builder.OpenComponent<TComponent>(42);
|
||||||
|
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||||
|
context.CodeWriter.Write(".");
|
||||||
|
context.CodeWriter.Write(BlazorApi.RenderTreeBuilder.OpenComponent);
|
||||||
|
context.CodeWriter.Write("<");
|
||||||
|
context.CodeWriter.Write(node.TypeName);
|
||||||
|
context.CodeWriter.Write(">(");
|
||||||
|
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||||
|
context.CodeWriter.Write(");");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentClose(CodeRenderingContext context, ComponentCloseExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The close tag counts as a child from a markup point of view.
|
||||||
|
_scopeStack.IncrementCurrentScopeChildCount(context);
|
||||||
|
|
||||||
|
// builder.OpenComponent<TComponent>(42);
|
||||||
|
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||||
|
context.CodeWriter.Write(".");
|
||||||
|
context.CodeWriter.Write(BlazorApi.RenderTreeBuilder.CloseComponent);
|
||||||
|
context.CodeWriter.Write("();");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentBody(CodeRenderingContext context, ComponentBodyExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
_scopeStack.OpenScope(node.TagName, isComponent: true);
|
||||||
|
context.RenderChildren(node);
|
||||||
|
_scopeStack.CloseScope(context, node.TagName, isComponent: true, source: node.Source);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node)
|
||||||
|
{
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
// builder.OpenComponent<TComponent>(42);
|
||||||
|
context.CodeWriter.Write(_scopeStack.BuilderVarName);
|
||||||
|
context.CodeWriter.Write(".");
|
||||||
|
context.CodeWriter.Write(BlazorApi.RenderTreeBuilder.AddAttribute);
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
context.CodeWriter.Write((_sourceSequence++).ToString());
|
||||||
|
context.CodeWriter.Write(", ");
|
||||||
|
context.CodeWriter.WriteStringLiteral(node.AttributeName);
|
||||||
|
context.CodeWriter.Write(", ");
|
||||||
|
|
||||||
|
if (node.AttributeStructure == AttributeStructure.Minimized)
|
||||||
|
{
|
||||||
|
// Minimized attributes always map to 'true'
|
||||||
|
context.CodeWriter.Write("true");
|
||||||
|
}
|
||||||
|
else if (
|
||||||
|
node.Children.Count != 1 ||
|
||||||
|
node.Children[0] is HtmlContentIntermediateNode htmlNode && htmlNode.Children.Count != 1 ||
|
||||||
|
node.Children[0] is CSharpExpressionIntermediateNode cSharpNode && cSharpNode.Children.Count != 1)
|
||||||
|
{
|
||||||
|
// We don't expect this to happen, we just want to know if it can.
|
||||||
|
throw new InvalidOperationException("Attribute nodes should either be minimized or a single content node.");
|
||||||
|
}
|
||||||
|
else if (node.BoundAttribute.IsUIEventHandlerProperty())
|
||||||
|
{
|
||||||
|
// This is a UIEventHandler property. We do some special code generation for this
|
||||||
|
// case so that it's easier to write for common cases.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// <MyComponent OnClick="Foo()"/>
|
||||||
|
// --> builder.AddAttribute(X, "OnClick", new UIEventHandler((e) => Foo()));
|
||||||
|
//
|
||||||
|
// The constructor is important because we want to put type inference into a state where
|
||||||
|
// we know the delegate's type should be UIEventHandler. AddAttribute has an overload that
|
||||||
|
// accepts object, so without the 'new UIEventHandler' things will get ugly.
|
||||||
|
//
|
||||||
|
// The escape for this behavior is to prefix the expression with @. This is similar to
|
||||||
|
// how escaping works for ModelExpression in MVC.
|
||||||
|
// Example:
|
||||||
|
// <MyComponent OnClick="@Foo"/>
|
||||||
|
// --> builder.AddAttribute(X, "OnClick", new UIEventHandler(Foo));
|
||||||
|
if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null)
|
||||||
|
{
|
||||||
|
// This is an escaped event handler;
|
||||||
|
context.CodeWriter.Write("new ");
|
||||||
|
context.CodeWriter.Write(node.BoundAttribute.TypeName);
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
context.CodeWriter.Write(((IntermediateToken)cSharpNode.Children[0]).Content);
|
||||||
|
context.CodeWriter.Write(")");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write("new ");
|
||||||
|
context.CodeWriter.Write(node.BoundAttribute.TypeName);
|
||||||
|
context.CodeWriter.Write("(");
|
||||||
|
context.CodeWriter.Write("e => ");
|
||||||
|
context.CodeWriter.Write(((IntermediateToken)node.Children[0]).Content);
|
||||||
|
context.CodeWriter.Write(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ((cSharpNode = node.Children[0] as CSharpExpressionIntermediateNode) != null)
|
||||||
|
{
|
||||||
|
context.CodeWriter.Write(((IntermediateToken)cSharpNode.Children[0]).Content);
|
||||||
|
}
|
||||||
|
else if ((htmlNode = node.Children[0] as HtmlContentIntermediateNode) != null)
|
||||||
|
{
|
||||||
|
// This is how string attributes are lowered by default, a single HTML node with a single HTML token.
|
||||||
|
context.CodeWriter.WriteStringLiteral(((IntermediateToken)htmlNode.Children[0]).Content);
|
||||||
|
}
|
||||||
|
else if (node.Children[0] is IntermediateToken token)
|
||||||
|
{
|
||||||
|
// This is what we expect for non-string nodes.
|
||||||
|
context.CodeWriter.Write(((IntermediateToken)node.Children[0]).Content);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Unexpected node type " + node.Children[0].GetType().FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
context.CodeWriter.Write(");");
|
||||||
|
context.CodeWriter.WriteLine();
|
||||||
|
}
|
||||||
|
|
||||||
private SourceSpan? CalculateSourcePosition(
|
private SourceSpan? CalculateSourcePosition(
|
||||||
SourceSpan? razorTokenPosition,
|
SourceSpan? razorTokenPosition,
|
||||||
TextPosition htmlNodePosition)
|
TextPosition htmlNodePosition)
|
||||||
|
|
@ -434,7 +591,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
codeWriter.WriteEndMethodInvocation();
|
codeWriter.WriteEndMethodInvocation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
||||||
{
|
{
|
||||||
codeWriter
|
codeWriter
|
||||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(BlazorApi.RenderTreeBuilder.AddAttribute)}")
|
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(BlazorApi.RenderTreeBuilder.AddAttribute)}")
|
||||||
|
|
@ -444,11 +601,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
.WriteParameterSeparator();
|
.WriteParameterSeparator();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
|
||||||
{
|
|
||||||
context.CodeWriter.WriteUsing(node.Content, endLine: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetContent(HtmlContentIntermediateNode node)
|
private static string GetContent(HtmlContentIntermediateNode node)
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class ComponentAttributeExtensionNode : ExtensionIntermediateNode
|
||||||
|
{
|
||||||
|
public ComponentAttributeExtensionNode()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentAttributeExtensionNode(TagHelperPropertyIntermediateNode propertyNode)
|
||||||
|
{
|
||||||
|
if (propertyNode == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(propertyNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
AttributeName = propertyNode.AttributeName;
|
||||||
|
AttributeStructure = propertyNode.AttributeStructure;
|
||||||
|
BoundAttribute = propertyNode.BoundAttribute;
|
||||||
|
IsIndexerNameMatch = propertyNode.IsIndexerNameMatch;
|
||||||
|
Source = propertyNode.Source;
|
||||||
|
TagHelper = propertyNode.TagHelper;
|
||||||
|
|
||||||
|
for (var i = 0; i < propertyNode.Children.Count; i++)
|
||||||
|
{
|
||||||
|
Children.Add(propertyNode.Children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
|
||||||
|
{
|
||||||
|
Diagnostics.Add(propertyNode.Diagnostics[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||||
|
|
||||||
|
public string AttributeName { get; set; }
|
||||||
|
|
||||||
|
public AttributeStructure AttributeStructure { get; set; }
|
||||||
|
|
||||||
|
public BoundAttributeDescriptor BoundAttribute { get; set; }
|
||||||
|
|
||||||
|
public string FieldName { get; set; }
|
||||||
|
|
||||||
|
public bool IsIndexerNameMatch { get; set; }
|
||||||
|
|
||||||
|
public string PropertyName { get; set; }
|
||||||
|
|
||||||
|
public TagHelperDescriptor TagHelper { get; set; }
|
||||||
|
|
||||||
|
public override void Accept(IntermediateNodeVisitor visitor)
|
||||||
|
{
|
||||||
|
if (visitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptExtensionNode<ComponentAttributeExtensionNode>(this, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||||
|
writer.WriteComponentAttribute(context, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
public sealed class ComponentBodyExtensionNode : ExtensionIntermediateNode
|
||||||
|
{
|
||||||
|
public ComponentBodyExtensionNode()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public ComponentBodyExtensionNode(TagHelperBodyIntermediateNode bodyNode)
|
||||||
|
{
|
||||||
|
if (bodyNode == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(bodyNode));
|
||||||
|
}
|
||||||
|
|
||||||
|
Source = bodyNode.Source;
|
||||||
|
|
||||||
|
for (var i = 0; i < bodyNode.Children.Count; i++)
|
||||||
|
{
|
||||||
|
Children.Add(bodyNode.Children[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < bodyNode.Diagnostics.Count; i++)
|
||||||
|
{
|
||||||
|
Diagnostics.Add(bodyNode.Diagnostics[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override IntermediateNodeCollection Children { get; } = new IntermediateNodeCollection();
|
||||||
|
|
||||||
|
public TagMode TagMode { get; set; }
|
||||||
|
|
||||||
|
public string TagName { get; set; }
|
||||||
|
|
||||||
|
public override void Accept(IntermediateNodeVisitor visitor)
|
||||||
|
{
|
||||||
|
if (visitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptExtensionNode<ComponentBodyExtensionNode>(this, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||||
|
writer.WriteComponentBody(context, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class ComponentCloseExtensionNode : ExtensionIntermediateNode
|
||||||
|
{
|
||||||
|
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||||
|
|
||||||
|
public override void Accept(IntermediateNodeVisitor visitor)
|
||||||
|
{
|
||||||
|
if (visitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptExtensionNode<ComponentCloseExtensionNode>(this, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||||
|
writer.WriteComponentClose(context, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,135 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class ComponentLoweringPass : IntermediateNodePassBase, IRazorOptimizationPass
|
||||||
|
{
|
||||||
|
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||||
|
{
|
||||||
|
var @namespace = documentNode.FindPrimaryNamespace();
|
||||||
|
var @class = documentNode.FindPrimaryClass();
|
||||||
|
if (@namespace == null || @class == null)
|
||||||
|
{
|
||||||
|
// Nothing to do, bail. We can't function without the standard structure.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each component *usage* we need to rewrite the tag helper node to map to the relevant component
|
||||||
|
// APIs.
|
||||||
|
var nodes = documentNode.FindDescendantNodes<TagHelperIntermediateNode>();
|
||||||
|
for (var i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
var node = nodes[i];
|
||||||
|
if (node.TagHelpers.Count > 1)
|
||||||
|
{
|
||||||
|
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
|
||||||
|
}
|
||||||
|
|
||||||
|
RewriteUsage(node, node.TagHelpers[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RewriteUsage(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
|
||||||
|
{
|
||||||
|
// Ignore Kind here. Some versions of Razor have a bug in the serializer that ignores it.
|
||||||
|
|
||||||
|
// We need to surround the contents of the node with open and close nodes to ensure the component
|
||||||
|
// is scoped correctly.
|
||||||
|
node.Children.Insert(0, new ComponentOpenExtensionNode()
|
||||||
|
{
|
||||||
|
TypeName = tagHelper.GetTypeName(),
|
||||||
|
});
|
||||||
|
|
||||||
|
for (var i = node.Children.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is TagHelperBodyIntermediateNode bodyNode)
|
||||||
|
{
|
||||||
|
// Replace with a node that we recognize so that it we can do proper scope tracking.
|
||||||
|
//
|
||||||
|
// Note that we force the body node to be last, this is done to push it after the
|
||||||
|
// attribute nodes. This gives us the ordering we want for the render tree.
|
||||||
|
node.Children.RemoveAt(i);
|
||||||
|
node.Children.Add(new ComponentBodyExtensionNode(bodyNode)
|
||||||
|
{
|
||||||
|
TagMode = node.TagMode,
|
||||||
|
TagName = node.TagName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Children.Add(new ComponentCloseExtensionNode());
|
||||||
|
|
||||||
|
// Now we need to rewrite any set property nodes to call the appropriate AddAttribute api.
|
||||||
|
for (var i = node.Children.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode &&
|
||||||
|
propertyNode.TagHelper == tagHelper)
|
||||||
|
{
|
||||||
|
// We don't support 'complex' content for components (mixed C# and markup) right now.
|
||||||
|
// It's not clear yet if Blazor will have a good scenario to use these constructs.
|
||||||
|
//
|
||||||
|
// This is where a lot of the complexity in the Razor/TagHelpers model creeps in and we
|
||||||
|
// might be able to avoid it if these features aren't needed.
|
||||||
|
if (propertyNode.Children.Count == 1 &&
|
||||||
|
propertyNode.Children[0] is HtmlAttributeIntermediateNode htmlNode &&
|
||||||
|
htmlNode.Children.Count > 1)
|
||||||
|
{
|
||||||
|
// This case can be hit for a 'string' attribute
|
||||||
|
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
|
||||||
|
propertyNode.Source,
|
||||||
|
propertyNode,
|
||||||
|
htmlNode.Children));
|
||||||
|
node.Children.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (propertyNode.Children.Count == 1 &&
|
||||||
|
propertyNode.Children[0] is CSharpExpressionIntermediateNode cSharpNode &&
|
||||||
|
cSharpNode.Children.Count > 1)
|
||||||
|
{
|
||||||
|
// This case can be hit when the attribute has an explicit @ inside, which
|
||||||
|
// 'escapes' any special sugar we provide for codegen.
|
||||||
|
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
|
||||||
|
propertyNode.Source,
|
||||||
|
propertyNode,
|
||||||
|
cSharpNode.Children));
|
||||||
|
node.Children.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (propertyNode.Children.Count > 1)
|
||||||
|
{
|
||||||
|
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
|
||||||
|
propertyNode.Source,
|
||||||
|
propertyNode,
|
||||||
|
propertyNode.Children));
|
||||||
|
node.Children.RemoveAt(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.Children[i] = new ComponentAttributeExtensionNode(propertyNode)
|
||||||
|
{
|
||||||
|
PropertyName = propertyNode.BoundAttribute.GetPropertyName(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add an error and remove any nodes that don't map to a component property.
|
||||||
|
for (var i = node.Children.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode attributeNode)
|
||||||
|
{
|
||||||
|
node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnboundComponentAttribute(
|
||||||
|
attributeNode.Source,
|
||||||
|
tagHelper.GetTypeName(),
|
||||||
|
attributeNode));
|
||||||
|
node.Children.RemoveAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal class ComponentOpenExtensionNode : ExtensionIntermediateNode
|
||||||
|
{
|
||||||
|
public override IntermediateNodeCollection Children => IntermediateNodeCollection.ReadOnly;
|
||||||
|
|
||||||
|
public string TypeName { get; set; }
|
||||||
|
|
||||||
|
public override void Accept(IntermediateNodeVisitor visitor)
|
||||||
|
{
|
||||||
|
if (visitor == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(visitor));
|
||||||
|
}
|
||||||
|
|
||||||
|
AcceptExtensionNode<ComponentOpenExtensionNode>(this, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteNode(CodeTarget target, CodeRenderingContext context)
|
||||||
|
{
|
||||||
|
if (target == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer = (BlazorNodeWriter)context.NodeWriter;
|
||||||
|
writer.WriteComponentOpen(context, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,8 +11,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
{
|
{
|
||||||
internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
|
internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, ITagHelperDescriptorProvider
|
||||||
{
|
{
|
||||||
public readonly static string ComponentTagHelperKind = ComponentDocumentClassifierPass.ComponentDocumentKind;
|
public static readonly string UIEventHandlerPropertyMetadata = "Blazor.IsUIEventHandler";
|
||||||
|
|
||||||
|
public readonly static string ComponentTagHelperKind = ComponentDocumentClassifierPass.ComponentDocumentKind;
|
||||||
|
|
||||||
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
|
private static readonly SymbolDisplayFormat FullNameTypeDisplayFormat =
|
||||||
SymbolDisplayFormat.FullyQualifiedFormat
|
SymbolDisplayFormat.FullyQualifiedFormat
|
||||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
|
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
|
||||||
|
|
@ -109,6 +111,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
pb.IsEnum = true;
|
pb.IsEnum = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (property.kind == PropertyKind.Delegate)
|
||||||
|
{
|
||||||
|
pb.Metadata.Add(UIEventHandlerPropertyMetadata, bool.TrueString);
|
||||||
|
}
|
||||||
|
|
||||||
xml = property.property.GetDocumentationCommentXml();
|
xml = property.property.GetDocumentationCommentXml();
|
||||||
if (!string.IsNullOrEmpty(xml))
|
if (!string.IsNullOrEmpty(xml))
|
||||||
{
|
{
|
||||||
|
|
@ -145,13 +152,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
var kind = PropertyKind.Default;
|
|
||||||
if (properties.ContainsKey(property.Name))
|
if (properties.ContainsKey(property.Name))
|
||||||
{
|
{
|
||||||
// Not visible
|
// Not visible
|
||||||
kind = PropertyKind.Ignored;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var kind = PropertyKind.Default;
|
||||||
if (property.Parameters.Length != 0)
|
if (property.Parameters.Length != 0)
|
||||||
{
|
{
|
||||||
// Indexer
|
// Indexer
|
||||||
|
|
@ -175,6 +182,15 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
kind = PropertyKind.Enum;
|
kind = PropertyKind.Enum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (kind == PropertyKind.Default &&
|
||||||
|
property.Type.TypeKind == TypeKind.Delegate &&
|
||||||
|
property.Type.ToDisplayString(FullNameTypeDisplayFormat) == BlazorApi.UIEventHandler.FullTypeName)
|
||||||
|
{
|
||||||
|
// For delegate types we do some special code generation when the type
|
||||||
|
// UIEventHandler.
|
||||||
|
kind = PropertyKind.Delegate;
|
||||||
|
}
|
||||||
|
|
||||||
properties.Add(property.Name, (property, kind));
|
properties.Add(property.Name, (property, kind));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -190,6 +206,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
Ignored,
|
Ignored,
|
||||||
Default,
|
Default,
|
||||||
Enum,
|
Enum,
|
||||||
|
Delegate,
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ComponentTypeVisitor : SymbolVisitor
|
private class ComponentTypeVisitor : SymbolVisitor
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
{
|
{
|
||||||
// When we're about to insert the first child into a component,
|
// When we're about to insert the first child into a component,
|
||||||
// it's time to open a new lambda
|
// it's time to open a new lambda
|
||||||
var blazorNodeWriter = (BlazorIntermediateNodeWriter)context.NodeWriter;
|
var blazorNodeWriter = (BlazorNodeWriter)context.NodeWriter;
|
||||||
blazorNodeWriter.BeginWriteAttribute(context.CodeWriter, BlazorApi.RenderTreeBuilder.ChildContent);
|
blazorNodeWriter.BeginWriteAttribute(context.CodeWriter, BlazorApi.RenderTreeBuilder.ChildContent);
|
||||||
OffsetBuilderVarNumber(1);
|
OffsetBuilderVarNumber(1);
|
||||||
context.CodeWriter.Write($"({BlazorApi.RenderFragment.FullTypeName})(");
|
context.CodeWriter.Write($"({BlazorApi.RenderFragment.FullTypeName})(");
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||||
|
{
|
||||||
|
internal static class TagHelperBoundAttributeDescriptorExtensions
|
||||||
|
{
|
||||||
|
public static bool IsUIEventHandlerProperty(this BoundAttributeDescriptor attribute)
|
||||||
|
{
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(attribute));
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = ComponentTagHelperDescriptorProvider.UIEventHandlerPropertyMetadata;
|
||||||
|
return
|
||||||
|
attribute.Metadata.TryGetValue(key, out var value) &&
|
||||||
|
string.Equals(value, bool.TrueString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// 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.Blazor.Components;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.RenderTree
|
namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
{
|
{
|
||||||
|
|
@ -224,5 +224,34 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||||
|
|
||||||
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
|
internal RenderTreeFrame WithRegionSubtreeLength(int regionSubtreeLength)
|
||||||
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
|
=> new RenderTreeFrame(Sequence, regionSubtreeLength: regionSubtreeLength);
|
||||||
|
|
||||||
|
// Just to be nice for debugging and unit tests.
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
switch (FrameType)
|
||||||
|
{
|
||||||
|
case RenderTreeFrameType.Attribute:
|
||||||
|
return $"Attribute: (seq={Sequence}, id={AttributeEventHandlerId}) '{AttributeName}'='{AttributeValue}'";
|
||||||
|
|
||||||
|
case RenderTreeFrameType.Component:
|
||||||
|
return $"Component: (seq={Sequence}, len={ComponentSubtreeLength}) {ComponentType}";
|
||||||
|
|
||||||
|
case RenderTreeFrameType.Element:
|
||||||
|
return $"Element: (seq={Sequence}, len={ElementSubtreeLength}) {ElementName}";
|
||||||
|
|
||||||
|
case RenderTreeFrameType.Region:
|
||||||
|
return $"Region: (seq={Sequence}, len={RegionSubtreeLength})";
|
||||||
|
|
||||||
|
case RenderTreeFrameType.Text:
|
||||||
|
return $"Text: (seq={Sequence}, len=n/a) {EscapeNewlines(TextContent)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string EscapeNewlines(string text)
|
||||||
|
{
|
||||||
|
return text.Replace("\n", "\\n").Replace("\r\n", "\\r\\n");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// 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.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -18,7 +21,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
using Microsoft.AspNetCore.Blazor.Components;
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
using namespace Test
|
namespace Test
|
||||||
{
|
{
|
||||||
public class MyComponent : BlazorComponent
|
public class MyComponent : BlazorComponent
|
||||||
{
|
{
|
||||||
|
|
@ -34,6 +37,29 @@ using namespace Test
|
||||||
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.MyComponent");
|
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.MyComponent");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ComponentDiscovery_CanFindComponent_WithNamespace_DefinedinCSharp()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test.AnotherNamespace
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var result = CompileToCSharp("@addTagHelper *, TestAssembly");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var bindings = result.CodeDocument.GetTagHelperContext();
|
||||||
|
Assert.Single(bindings.TagHelpers, t => t.Name == "Test.AnotherNamespace.MyComponent");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void ComponentDiscovery_CanFindComponent_DefinedinCshtml()
|
public void ComponentDiscovery_CanFindComponent_DefinedinCshtml()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2,79 +2,285 @@
|
||||||
// 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.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNetCore.Blazor.Components;
|
|
||||||
using Microsoft.AspNetCore.Blazor.Layouts;
|
|
||||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||||
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
{
|
{
|
||||||
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
|
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
|
||||||
{
|
{
|
||||||
|
internal override bool UseTwoPhaseCompilation => true;
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void SupportsChildComponentsViaTemporarySyntax()
|
public void Render_ChildComponent_Simple()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange
|
||||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
var component = CompileToComponent($"<c:{testComponentTypeName} />");
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent/>");
|
||||||
|
|
||||||
|
// Act
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(frames,
|
Assert.Collection(
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 1, 0));
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanPassParametersToComponents()
|
public void Render_ChildComponent_WithParameters()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange
|
||||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
var testObjectTypeName = FullTypeName<SomeType>();
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
// TODO: Once we have the improved component tooling and can allow syntax
|
|
||||||
// like StringProperty="My string" or BoolProperty=true, update this
|
namespace Test
|
||||||
// test to use that syntax.
|
{
|
||||||
var component = CompileToComponent($"<c:{testComponentTypeName}" +
|
public class SomeType
|
||||||
$" IntProperty=@(123)" +
|
{
|
||||||
$" BoolProperty=@true" +
|
}
|
||||||
$" StringProperty=@(\"My string\")" +
|
|
||||||
$" ObjectProperty=@(new {testObjectTypeName}()) />");
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public int IntProperty { get; set; }
|
||||||
|
public bool BoolProperty { get; set; }
|
||||||
|
public string StringProperty { get; set; }
|
||||||
|
public SomeType ObjectProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent
|
||||||
|
IntProperty=""123""
|
||||||
|
BoolProperty=""true""
|
||||||
|
StringProperty=""My string""
|
||||||
|
ObjectProperty=""new SomeType()""/>");
|
||||||
|
|
||||||
|
// Act
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Collection(frames,
|
Assert.Collection(
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 5, 0),
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
||||||
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
||||||
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
||||||
frame =>
|
frame =>
|
||||||
{
|
{
|
||||||
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
||||||
Assert.IsType<SomeType>(frame.AttributeValue);
|
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanIncludeChildrenInComponents()
|
public void Render_ChildComponent_WithExplicitStringParameter()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange
|
||||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
var component = CompileToComponent($"<c:{testComponentTypeName} MyAttr=@(\"abc\")>" +
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
$"Some text" +
|
|
||||||
$"<some-child a='1'>Nested text</some-child>" +
|
namespace Test
|
||||||
$"</c:{testComponentTypeName}>");
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public string StringProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent StringProperty=""@(42.ToString())"" />");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||||
|
frame => AssertFrame.Attribute(frame, "StringProperty", "42", 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Render_ChildComponent_WithLambdaEventHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent OnClick=""Increment()""/>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private int counter;
|
||||||
|
private void Increment() {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
AssertFrame.Attribute(frame, "OnClick", 1);
|
||||||
|
|
||||||
|
// The handler will have been assigned to a lambda
|
||||||
|
var handler = Assert.IsType<UIEventHandler>(frame.AttributeValue);
|
||||||
|
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
||||||
|
},
|
||||||
|
frame => AssertFrame.Whitespace(frame, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Render_ChildComponent_WithExplicitEventHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
@using Microsoft.AspNetCore.Blazor
|
||||||
|
<MyComponent OnClick=""@Increment""/>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private int counter;
|
||||||
|
private void Increment(UIEventArgs e) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||||
|
frame =>
|
||||||
|
{
|
||||||
|
AssertFrame.Attribute(frame, "OnClick", 1);
|
||||||
|
|
||||||
|
// The handler will have been assigned to a lambda
|
||||||
|
var handler = Assert.IsType<UIEventHandler>(frame.AttributeValue);
|
||||||
|
Assert.Equal("Test.TestComponent", handler.Target.GetType().FullName);
|
||||||
|
Assert.Equal("Increment", handler.Method.Name);
|
||||||
|
},
|
||||||
|
frame => AssertFrame.Whitespace(frame, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Render_ChildComponent_WithMinimizedBoolAttribute()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public bool BoolProperty { get; set; }
|
||||||
|
}
|
||||||
|
}"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent BoolProperty />");
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Collection(
|
||||||
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||||
|
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Render_ChildComponent_WithChildContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public string MyAttr { get; set; }
|
||||||
|
|
||||||
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text</some-child></MyComponent>");
|
||||||
|
|
||||||
|
// Act
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
// Assert: component frames are correct
|
// Assert: component frames are correct
|
||||||
Assert.Collection(frames,
|
Assert.Collection(
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 3, 0),
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||||
frame => AssertFrame.Attribute(frame, "MyAttr", "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
|
||||||
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
|
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
|
||||||
Assert.Collection(childFrames,
|
Assert.Collection(
|
||||||
|
childFrames,
|
||||||
frame => AssertFrame.Text(frame, "Some text", 3),
|
frame => AssertFrame.Text(frame, "Some text", 3),
|
||||||
frame => AssertFrame.Element(frame, "some-child", 3, 4),
|
frame => AssertFrame.Element(frame, "some-child", 3, 4),
|
||||||
frame => AssertFrame.Attribute(frame, "a", "1", 5),
|
frame => AssertFrame.Attribute(frame, "a", "1", 5),
|
||||||
|
|
@ -82,21 +288,33 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanNestComponentChildContent()
|
public void Render_ChildComponent_Nested()
|
||||||
{
|
{
|
||||||
// Arrange/Act
|
// Arrange
|
||||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
var component = CompileToComponent(
|
using Microsoft.AspNetCore.Blazor;
|
||||||
$"<c:{testComponentTypeName}>" +
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
$"<c:{testComponentTypeName}>" +
|
|
||||||
$"Some text" +
|
namespace Test
|
||||||
$"</c:{testComponentTypeName}>" +
|
{
|
||||||
$"</c:{testComponentTypeName}>");
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
var component = CompileToComponent(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent><MyComponent>Some text</MyComponent></MyComponent>");
|
||||||
|
|
||||||
|
// Act
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
|
|
||||||
// Assert: outer component frames are correct
|
// Assert: outer component frames are correct
|
||||||
Assert.Collection(frames,
|
Assert.Collection(
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 2, 0),
|
frames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 1));
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 1));
|
||||||
|
|
||||||
// Assert: first level of ChildContent is correct
|
// Assert: first level of ChildContent is correct
|
||||||
|
|
@ -106,26 +324,15 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
// As an implementation detail, it happens that they do follow on from the parent
|
// As an implementation detail, it happens that they do follow on from the parent
|
||||||
// level, but we could change that part of the implementation if we wanted.
|
// level, but we could change that part of the implementation if we wanted.
|
||||||
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).ToArray();
|
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).ToArray();
|
||||||
Assert.Collection(innerFrames,
|
Assert.Collection(
|
||||||
frame => AssertFrame.Component<TestComponent>(frame, 2, 2),
|
innerFrames,
|
||||||
|
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2),
|
||||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 3));
|
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 3));
|
||||||
|
|
||||||
// Assert: second level of ChildContent is correct
|
// Assert: second level of ChildContent is correct
|
||||||
Assert.Collection(GetFrames((RenderFragment)innerFrames[1].AttributeValue),
|
Assert.Collection(
|
||||||
|
GetFrames((RenderFragment)innerFrames[1].AttributeValue),
|
||||||
frame => AssertFrame.Text(frame, "Some text", 4));
|
frame => AssertFrame.Text(frame, "Some text", 4));
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SomeType { }
|
|
||||||
|
|
||||||
public class TestComponent : IComponent
|
|
||||||
{
|
|
||||||
public void Init(RenderHandle renderHandle)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetParameters(ParameterCollection parameters)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ using System.Reflection;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using Microsoft.AspNetCore.Blazor.Components;
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
using Microsoft.AspNetCore.Blazor.Razor;
|
using Microsoft.AspNetCore.Blazor.Razor;
|
||||||
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
|
||||||
using Microsoft.AspNetCore.Razor.Language;
|
using Microsoft.AspNetCore.Razor.Language;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,431 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
{
|
||||||
|
public class DesignTimeCodeGenerationRazorIntegrationTest : RazorIntegrationTestBase
|
||||||
|
{
|
||||||
|
internal override bool DesignTime => true;
|
||||||
|
|
||||||
|
internal override bool UseTwoPhaseCompilation => true;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class SomeType
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public int IntProperty { get; set; }
|
||||||
|
public bool BoolProperty { get; set; }
|
||||||
|
public string StringProperty { get; set; }
|
||||||
|
public SomeType ObjectProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent
|
||||||
|
IntProperty=""123""
|
||||||
|
BoolProperty=""true""
|
||||||
|
StringProperty=""My string""
|
||||||
|
ObjectProperty=""new SomeType()""/>");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
((System.Action)(() => {
|
||||||
|
global::System.Object __typeHelper = ""*, TestAssembly"";
|
||||||
|
}
|
||||||
|
))();
|
||||||
|
}
|
||||||
|
#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.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
|
||||||
|
__o =
|
||||||
|
#line 3 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
123
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
;
|
||||||
|
__o =
|
||||||
|
#line 4 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
true
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
;
|
||||||
|
__o =
|
||||||
|
#line 6 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
new SomeType()
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
;
|
||||||
|
builder.AddAttribute(-1, ""ChildContent"", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithExplicitStringParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public string StringProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent StringProperty=""@(42.ToString())"" />");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
((System.Action)(() => {
|
||||||
|
global::System.Object __typeHelper = ""*, TestAssembly"";
|
||||||
|
}
|
||||||
|
))();
|
||||||
|
}
|
||||||
|
#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.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
|
||||||
|
__o =
|
||||||
|
#line 2 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
42.ToString()
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
;
|
||||||
|
builder.AddAttribute(-1, ""ChildContent"", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithLambdaEventHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent OnClick=""Increment()""/>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private int counter;
|
||||||
|
private void Increment() {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
((System.Action)(() => {
|
||||||
|
global::System.Object __typeHelper = ""*, TestAssembly"";
|
||||||
|
}
|
||||||
|
))();
|
||||||
|
}
|
||||||
|
#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.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
|
||||||
|
__o = new Microsoft.AspNetCore.Blazor.UIEventHandler(e =>
|
||||||
|
#line 2 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
Increment()
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
);
|
||||||
|
builder.AddAttribute(-1, ""ChildContent"", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 4 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private void Increment() {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithExplicitEventHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
@using Microsoft.AspNetCore.Blazor
|
||||||
|
<MyComponent OnClick=""@Increment""/>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private int counter;
|
||||||
|
private void Increment(UIEventArgs e) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
#line 2 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
((System.Action)(() => {
|
||||||
|
global::System.Object __typeHelper = ""*, TestAssembly"";
|
||||||
|
}
|
||||||
|
))();
|
||||||
|
}
|
||||||
|
#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.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
|
||||||
|
__o = new Microsoft.AspNetCore.Blazor.UIEventHandler(
|
||||||
|
#line 3 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
Increment
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
);
|
||||||
|
builder.AddAttribute(-1, ""ChildContent"", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
#line 5 ""x:\dir\subdir\Test\TestComponent.cshtml""
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private void Increment(UIEventArgs e) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
#line default
|
||||||
|
#line hidden
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
|
||||||
|
".Trim(), generated.Code.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithChildContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public string MyAttr { get; set; }
|
||||||
|
|
||||||
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text</some-child></MyComponent>");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 219
|
||||||
|
private void __RazorDirectiveTokenHelpers__() {
|
||||||
|
((System.Action)(() => {
|
||||||
|
global::System.Object __typeHelper = ""*, TestAssembly"";
|
||||||
|
}
|
||||||
|
))();
|
||||||
|
}
|
||||||
|
#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.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
|
||||||
|
builder.AddAttribute(-1, ""ChildContent"", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -56,14 +56,19 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
public RazorIntegrationTestBase()
|
public RazorIntegrationTestBase()
|
||||||
{
|
{
|
||||||
AdditionalSyntaxTrees = new List<SyntaxTree>();
|
AdditionalSyntaxTrees = new List<SyntaxTree>();
|
||||||
|
AdditionalRazorItems = new List<RazorProjectItem>();
|
||||||
|
|
||||||
Configuration = BlazorExtensionInitializer.DefaultConfiguration;
|
Configuration = BlazorExtensionInitializer.DefaultConfiguration;
|
||||||
FileSystem = new VirtualRazorProjectFileSystem();
|
FileSystem = new VirtualRazorProjectFileSystem();
|
||||||
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
|
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
|
||||||
|
|
||||||
DefaultBaseNamespace = "Test"; // Matches the default working directory
|
DefaultBaseNamespace = "Test"; // Matches the default working directory
|
||||||
DefaultFileName = "TestComponent.cshtml";
|
DefaultFileName = "TestComponent.cshtml";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal List<RazorProjectItem> AdditionalRazorItems { get; }
|
||||||
|
|
||||||
internal List<SyntaxTree> AdditionalSyntaxTrees { get; }
|
internal List<SyntaxTree> AdditionalSyntaxTrees { get; }
|
||||||
|
|
||||||
internal virtual RazorConfiguration Configuration { get; }
|
internal virtual RazorConfiguration Configuration { get; }
|
||||||
|
|
@ -71,6 +76,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
internal virtual string DefaultBaseNamespace { get; }
|
internal virtual string DefaultBaseNamespace { get; }
|
||||||
|
|
||||||
internal virtual string DefaultFileName { get; }
|
internal virtual string DefaultFileName { get; }
|
||||||
|
|
||||||
|
internal virtual bool DesignTime { get; }
|
||||||
|
|
||||||
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
|
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
|
||||||
|
|
||||||
|
|
@ -82,6 +89,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
{
|
{
|
||||||
return RazorProjectEngine.Create(configuration, FileSystem, b =>
|
return RazorProjectEngine.Create(configuration, FileSystem, b =>
|
||||||
{
|
{
|
||||||
|
// Turn off checksums, we're testing code generation.
|
||||||
|
b.Features.Add(new SuppressChecksum());
|
||||||
|
|
||||||
BlazorExtensionInitializer.Register(b);
|
BlazorExtensionInitializer.Register(b);
|
||||||
|
|
||||||
b.Features.Add(new CompilationTagHelperFeature());
|
b.Features.Add(new CompilationTagHelperFeature());
|
||||||
|
|
@ -92,58 +102,83 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlContent)
|
internal RazorProjectItem CreateProjectItem(string chtmlRelativePath, string cshtmlContent)
|
||||||
{
|
|
||||||
return CompileToCSharp(WorkingDirectory, DefaultFileName, cshtmlContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent)
|
|
||||||
{
|
|
||||||
return CompileToCSharp(WorkingDirectory, cshtmlRelativePath, cshtmlContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlRootPath, string cshtmlRelativePath, string cshtmlContent)
|
|
||||||
{
|
{
|
||||||
// FilePaths in Razor are **always** are of the form '/a/b/c.cshtml'
|
// FilePaths in Razor are **always** are of the form '/a/b/c.cshtml'
|
||||||
var filePath = cshtmlRelativePath.Replace('\\', '/');
|
var filePath = chtmlRelativePath.Replace('\\', '/');
|
||||||
if (!filePath.StartsWith('/'))
|
if (!filePath.StartsWith('/'))
|
||||||
{
|
{
|
||||||
filePath = '/' + filePath;
|
filePath = '/' + filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
var projectItem = new VirtualProjectItem(
|
return new VirtualProjectItem(
|
||||||
cshtmlRootPath,
|
WorkingDirectory,
|
||||||
filePath,
|
filePath,
|
||||||
Path.Combine(cshtmlRootPath, cshtmlRelativePath),
|
Path.Combine(WorkingDirectory, chtmlRelativePath),
|
||||||
cshtmlRelativePath,
|
chtmlRelativePath,
|
||||||
Encoding.UTF8.GetBytes(cshtmlContent));
|
Encoding.UTF8.GetBytes(cshtmlContent.TrimStart()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CompileToCSharpResult CompileToCSharp(string cshtmlContent)
|
||||||
|
{
|
||||||
|
return CompileToCSharp(DefaultFileName, cshtmlContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent)
|
||||||
|
{
|
||||||
if (UseTwoPhaseCompilation)
|
if (UseTwoPhaseCompilation)
|
||||||
{
|
{
|
||||||
// The first phase won't include any metadata references for component discovery. This mirrors
|
// The first phase won't include any metadata references for component discovery. This mirrors
|
||||||
// what the build does.
|
// what the build does.
|
||||||
var projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DeclarationConfiguration, Array.Empty<MetadataReference>());
|
var projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DeclarationConfiguration, Array.Empty<MetadataReference>());
|
||||||
var codeDocument = projectEngine.Process(projectItem);
|
|
||||||
|
RazorCodeDocument codeDocument;
|
||||||
|
foreach (var item in AdditionalRazorItems)
|
||||||
|
{
|
||||||
|
// Result of generating declarations
|
||||||
|
codeDocument = projectEngine.Process(item);
|
||||||
|
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
|
||||||
|
|
||||||
|
var syntaxTree = CSharpSyntaxTree.ParseText(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
|
||||||
|
AdditionalSyntaxTrees.Add(syntaxTree);
|
||||||
|
}
|
||||||
|
|
||||||
// Result of generating declarations
|
// Result of generating declarations
|
||||||
|
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||||
|
codeDocument = projectEngine.Process(projectItem);
|
||||||
var declaration = new CompileToCSharpResult
|
var declaration = new CompileToCSharpResult
|
||||||
{
|
{
|
||||||
|
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||||
CodeDocument = codeDocument,
|
CodeDocument = codeDocument,
|
||||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Result of doing 'temp' compilation
|
// Result of doing 'temp' compilation
|
||||||
var tempAssembly = CompileToAssembly(declaration, BaseCompilation);
|
var tempAssembly = CompileToAssembly(declaration);
|
||||||
|
|
||||||
// Add the 'temp' compilation as a metadata reference
|
// Add the 'temp' compilation as a metadata reference
|
||||||
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
|
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
|
||||||
projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DefaultConfiguration, references);
|
projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DefaultConfiguration, references);
|
||||||
|
|
||||||
// Result of real code
|
// Now update the any additional files
|
||||||
codeDocument = projectEngine.Process(projectItem);
|
foreach (var item in AdditionalRazorItems)
|
||||||
|
{
|
||||||
|
// Result of generating declarations
|
||||||
|
codeDocument = projectEngine.Process(item);
|
||||||
|
Assert.Empty(codeDocument.GetCSharpDocument().Diagnostics);
|
||||||
|
|
||||||
|
// Replace the 'declaration' syntax tree
|
||||||
|
var syntaxTree = CSharpSyntaxTree.ParseText(codeDocument.GetCSharpDocument().GeneratedCode, path: item.FilePath);
|
||||||
|
AdditionalSyntaxTrees.RemoveAll(st => st.FilePath == item.FilePath);
|
||||||
|
AdditionalSyntaxTrees.Add(syntaxTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Result of real code generation for the document under test
|
||||||
|
codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
|
||||||
return new CompileToCSharpResult
|
return new CompileToCSharpResult
|
||||||
{
|
{
|
||||||
|
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||||
CodeDocument = codeDocument,
|
CodeDocument = codeDocument,
|
||||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||||
|
|
@ -155,9 +190,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
// This will include the built-in Blazor components.
|
// This will include the built-in Blazor components.
|
||||||
var projectEngine = CreateProjectEngine(Configuration, BaseCompilation.References.ToArray());
|
var projectEngine = CreateProjectEngine(Configuration, BaseCompilation.References.ToArray());
|
||||||
|
|
||||||
var codeDocument = projectEngine.Process(projectItem);
|
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||||
|
var codeDocument = DesignTime ? projectEngine.ProcessDesignTime(projectItem) : projectEngine.Process(projectItem);
|
||||||
return new CompileToCSharpResult
|
return new CompileToCSharpResult
|
||||||
{
|
{
|
||||||
|
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||||
CodeDocument = codeDocument,
|
CodeDocument = codeDocument,
|
||||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||||
|
|
@ -167,19 +204,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
|
||||||
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent)
|
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent)
|
||||||
{
|
{
|
||||||
return CompileToAssembly(WorkingDirectory, cshtmlRelativePath, cshtmlContent);
|
var cSharpResult = CompileToCSharp(cshtmlRelativePath, cshtmlContent);
|
||||||
}
|
|
||||||
|
|
||||||
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRootDirectory, string cshtmlRelativePath, string cshtmlContent)
|
|
||||||
{
|
|
||||||
var cSharpResult = CompileToCSharp(cshtmlRootDirectory, cshtmlRelativePath, cshtmlContent);
|
|
||||||
return CompileToAssembly(cSharpResult);
|
return CompileToAssembly(cSharpResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult, CSharpCompilation baseCompilation = null)
|
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult)
|
||||||
{
|
{
|
||||||
baseCompilation = baseCompilation ?? BaseCompilation;
|
|
||||||
|
|
||||||
if (cSharpResult.Diagnostics.Any())
|
if (cSharpResult.Diagnostics.Any())
|
||||||
{
|
{
|
||||||
var diagnosticsLog = string.Join(Environment.NewLine, cSharpResult.Diagnostics.Select(d => d.ToString()).ToArray());
|
var diagnosticsLog = string.Join(Environment.NewLine, cSharpResult.Diagnostics.Select(d => d.ToString()).ToArray());
|
||||||
|
|
@ -188,17 +218,24 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
|
||||||
var syntaxTrees = new[]
|
var syntaxTrees = new[]
|
||||||
{
|
{
|
||||||
CSharpSyntaxTree.ParseText(cSharpResult.Code)
|
CSharpSyntaxTree.ParseText(cSharpResult.Code),
|
||||||
};
|
};
|
||||||
|
|
||||||
var compilation = baseCompilation.AddSyntaxTrees(syntaxTrees).AddSyntaxTrees(AdditionalSyntaxTrees);
|
var compilation = cSharpResult.BaseCompilation.AddSyntaxTrees(syntaxTrees);
|
||||||
|
|
||||||
|
var diagnostics = compilation
|
||||||
|
.GetDiagnostics()
|
||||||
|
.Where(d => d.Severity != DiagnosticSeverity.Hidden);
|
||||||
|
|
||||||
|
if (diagnostics.Any())
|
||||||
|
{
|
||||||
|
throw new CompilationFailedException(compilation);
|
||||||
|
}
|
||||||
|
|
||||||
using (var peStream = new MemoryStream())
|
using (var peStream = new MemoryStream())
|
||||||
{
|
{
|
||||||
compilation.Emit(peStream);
|
compilation.Emit(peStream);
|
||||||
|
|
||||||
var diagnostics = compilation
|
|
||||||
.GetDiagnostics()
|
|
||||||
.Where(d => d.Severity != DiagnosticSeverity.Hidden);
|
|
||||||
return new CompileToAssemblyResult
|
return new CompileToAssemblyResult
|
||||||
{
|
{
|
||||||
Compilation = compilation,
|
Compilation = compilation,
|
||||||
|
|
@ -206,11 +243,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray())
|
Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IComponent CompileToComponent(string cshtmlSource)
|
protected IComponent CompileToComponent(string cshtmlSource)
|
||||||
{
|
{
|
||||||
var assemblyResult = CompileToAssembly(WorkingDirectory, DefaultFileName, cshtmlSource);
|
var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource);
|
||||||
|
|
||||||
var componentFullTypeName = $"{DefaultBaseNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}";
|
var componentFullTypeName = $"{DefaultBaseNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}";
|
||||||
return CompileToComponent(assemblyResult, componentFullTypeName);
|
return CompileToComponent(assemblyResult, componentFullTypeName);
|
||||||
|
|
@ -223,8 +261,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
|
||||||
protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName)
|
protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName)
|
||||||
{
|
{
|
||||||
Assert.Empty(assemblyResult.Diagnostics);
|
|
||||||
|
|
||||||
var componentType = assemblyResult.Assembly.GetType(fullTypeName);
|
var componentType = assemblyResult.Assembly.GetType(fullTypeName);
|
||||||
if (componentType == null)
|
if (componentType == null)
|
||||||
{
|
{
|
||||||
|
|
@ -255,6 +291,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
|
||||||
protected class CompileToCSharpResult
|
protected class CompileToCSharpResult
|
||||||
{
|
{
|
||||||
|
// A compilation that can be used *with* this code to compile an assembly
|
||||||
|
public Compilation BaseCompilation { get; set; }
|
||||||
public RazorCodeDocument CodeDocument { get; set; }
|
public RazorCodeDocument CodeDocument { get; set; }
|
||||||
public string Code { get; set; }
|
public string Code { get; set; }
|
||||||
public IEnumerable<RazorDiagnostic> Diagnostics { get; set; }
|
public IEnumerable<RazorDiagnostic> Diagnostics { get; set; }
|
||||||
|
|
@ -284,5 +322,62 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.ToArray();
|
LatestBatchReferenceFrames = renderBatch.ReferenceFrames.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CompilationFailedException : XunitException
|
||||||
|
{
|
||||||
|
public CompilationFailedException(Compilation compilation)
|
||||||
|
{
|
||||||
|
Compilation = compilation;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Compilation Compilation { get; }
|
||||||
|
|
||||||
|
public override string Message
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var builder = new StringBuilder();
|
||||||
|
builder.AppendLine("Compilation failed: ");
|
||||||
|
|
||||||
|
var diagnostics = Compilation.GetDiagnostics();
|
||||||
|
var syntaxTreesWithErrors = new HashSet<SyntaxTree>();
|
||||||
|
foreach (var diagnostic in diagnostics)
|
||||||
|
{
|
||||||
|
builder.AppendLine(diagnostic.ToString());
|
||||||
|
|
||||||
|
if (diagnostic.Location.IsInSource)
|
||||||
|
{
|
||||||
|
syntaxTreesWithErrors.Add(diagnostic.Location.SourceTree);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (syntaxTreesWithErrors.Any())
|
||||||
|
{
|
||||||
|
builder.AppendLine();
|
||||||
|
builder.AppendLine();
|
||||||
|
|
||||||
|
foreach (var syntaxTree in syntaxTreesWithErrors)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"File {syntaxTree.FilePath ?? "unknown"}:");
|
||||||
|
builder.AppendLine(syntaxTree.GetText().ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SuppressChecksum : IConfigureRazorCodeGenerationOptionsFeature
|
||||||
|
{
|
||||||
|
public int Order => 0;
|
||||||
|
|
||||||
|
public RazorEngine Engine { get; set; }
|
||||||
|
|
||||||
|
public void Configure(RazorCodeGenerationOptionsBuilder options)
|
||||||
|
{
|
||||||
|
options.SuppressChecksum = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,15 +43,14 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Whitespace(frame, 0),
|
frame => AssertFrame.Text(frame, "Hello", 0),
|
||||||
frame => AssertFrame.Text(frame, "Hello", 1),
|
frame => AssertFrame.Whitespace(frame, 1),
|
||||||
frame => AssertFrame.Whitespace(frame, 2),
|
frame => AssertFrame.Whitespace(frame, 2), // @((object)null)
|
||||||
frame => AssertFrame.Whitespace(frame, 3), // @((object)null)
|
frame => AssertFrame.Whitespace(frame, 3),
|
||||||
frame => AssertFrame.Whitespace(frame, 4),
|
frame => AssertFrame.Text(frame, "123", 4),
|
||||||
frame => AssertFrame.Text(frame, "123", 5),
|
frame => AssertFrame.Whitespace(frame, 5),
|
||||||
frame => AssertFrame.Whitespace(frame, 6),
|
frame => AssertFrame.Text(frame, new object().ToString(), 6),
|
||||||
frame => AssertFrame.Text(frame, new object().ToString(), 7),
|
frame => AssertFrame.Whitespace(frame, 7));
|
||||||
frame => AssertFrame.Whitespace(frame, 8));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -70,12 +69,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
// Assert
|
// Assert
|
||||||
var frames = GetRenderTree(component);
|
var frames = GetRenderTree(component);
|
||||||
Assert.Collection(frames,
|
Assert.Collection(frames,
|
||||||
frame => AssertFrame.Whitespace(frame, 0),
|
frame => AssertFrame.Text(frame, "First", 0),
|
||||||
frame => AssertFrame.Text(frame, "First", 1),
|
frame => AssertFrame.Text(frame, "Second", 0),
|
||||||
frame => AssertFrame.Text(frame, "Second", 1),
|
frame => AssertFrame.Text(frame, "Third", 0),
|
||||||
frame => AssertFrame.Text(frame, "Third", 1),
|
frame => AssertFrame.Whitespace(frame, 1),
|
||||||
frame => AssertFrame.Whitespace(frame, 2),
|
frame => AssertFrame.Whitespace(frame, 2));
|
||||||
frame => AssertFrame.Whitespace(frame, 3));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,374 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||||
|
{
|
||||||
|
public class RuntimeCodeGenerationRazorIntegrationTest : RazorIntegrationTestBase
|
||||||
|
{
|
||||||
|
internal override bool UseTwoPhaseCompilation => true;
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_Simple()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent />");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithParameters()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class SomeType
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public int IntProperty { get; set; }
|
||||||
|
public bool BoolProperty { get; set; }
|
||||||
|
public string StringProperty { get; set; }
|
||||||
|
public SomeType ObjectProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent
|
||||||
|
IntProperty=""123""
|
||||||
|
BoolProperty=""true""
|
||||||
|
StringProperty=""My string""
|
||||||
|
ObjectProperty=""new SomeType()""/>");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, ""IntProperty"", 123);
|
||||||
|
builder.AddAttribute(2, ""BoolProperty"", true);
|
||||||
|
builder.AddAttribute(3, ""StringProperty"", ""My string"");
|
||||||
|
builder.AddAttribute(4, ""ObjectProperty"", new SomeType());
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithExplicitStringParameter()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public string StringProperty { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent StringProperty=""@(42.ToString())"" />");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, ""StringProperty"", 42.ToString());
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithLambdaEventHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent OnClick=""Increment()""/>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private int counter;
|
||||||
|
private void Increment() {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, ""OnClick"", new Microsoft.AspNetCore.Blazor.UIEventHandler(e => Increment()));
|
||||||
|
builder.CloseComponent();
|
||||||
|
builder.AddContent(2, ""\n\n"");
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private void Increment() {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim(), ignoreLineEndingDifferences: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithExplicitEventHandler()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using System;
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
@using Microsoft.AspNetCore.Blazor
|
||||||
|
<MyComponent OnClick=""@Increment""/>
|
||||||
|
|
||||||
|
@functions {
|
||||||
|
private int counter;
|
||||||
|
private void Increment(UIEventArgs e) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <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.Blazor;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, ""OnClick"", new Microsoft.AspNetCore.Blazor.UIEventHandler(Increment));
|
||||||
|
builder.CloseComponent();
|
||||||
|
builder.AddContent(2, ""\n\n"");
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
|
||||||
|
private int counter;
|
||||||
|
private void Increment(UIEventArgs e) {
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CodeGeneration_ChildComponent_WithChildContent()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public string MyAttr { get; set; }
|
||||||
|
|
||||||
|
public RenderFragment ChildContent { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"));
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var generated = CompileToCSharp(@"
|
||||||
|
@addTagHelper *, TestAssembly
|
||||||
|
<MyComponent MyAttr=""abc"">Some text<some-child a='1'>Nested text</some-child></MyComponent>");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
CompileToAssembly(generated);
|
||||||
|
|
||||||
|
Assert.Equal(@"
|
||||||
|
// <auto-generated/>
|
||||||
|
#pragma warning disable 1591
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
#line hidden
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
public class TestComponent : Microsoft.AspNetCore.Blazor.Components.BlazorComponent
|
||||||
|
{
|
||||||
|
#pragma warning disable 1998
|
||||||
|
protected override void BuildRenderTree(Microsoft.AspNetCore.Blazor.RenderTree.RenderTreeBuilder builder)
|
||||||
|
{
|
||||||
|
base.BuildRenderTree(builder);
|
||||||
|
builder.OpenComponent<Test.MyComponent>(0);
|
||||||
|
builder.AddAttribute(1, ""MyAttr"", ""abc"");
|
||||||
|
builder.AddAttribute(2, ""ChildContent"", (Microsoft.AspNetCore.Blazor.RenderFragment)((builder2) => {
|
||||||
|
builder2.AddContent(3, ""Some text"");
|
||||||
|
builder2.OpenElement(4, ""some-child"");
|
||||||
|
builder2.AddAttribute(5, ""a"", ""1"");
|
||||||
|
builder2.AddContent(6, ""Nested text"");
|
||||||
|
builder2.CloseElement();
|
||||||
|
}
|
||||||
|
));
|
||||||
|
builder.CloseComponent();
|
||||||
|
}
|
||||||
|
#pragma warning restore 1998
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#pragma warning restore 1591
|
||||||
|
".Trim(), generated.Code.Trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -282,6 +282,53 @@ namespace Test
|
||||||
Assert.False(attribute.IsStringProperty);
|
Assert.False(attribute.IsStringProperty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact] // UIEventHandler properties have some special intellisense behavior
|
||||||
|
public void Excecute_UIEventHandlerProperty_CreatesDescriptor()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
var compilation = BaseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(@"
|
||||||
|
using Microsoft.AspNetCore.Blazor;
|
||||||
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
|
|
||||||
|
namespace Test
|
||||||
|
{
|
||||||
|
public class MyComponent : BlazorComponent
|
||||||
|
{
|
||||||
|
public UIEventHandler OnClick { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"));
|
||||||
|
|
||||||
|
Assert.Empty(compilation.GetDiagnostics());
|
||||||
|
|
||||||
|
var context = TagHelperDescriptorProviderContext.Create();
|
||||||
|
context.SetCompilation(compilation);
|
||||||
|
|
||||||
|
var provider = new ComponentTagHelperDescriptorProvider();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
provider.Execute(context);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var components = ExcludeBuiltInComponents(context);
|
||||||
|
var component = Assert.Single(components);
|
||||||
|
|
||||||
|
Assert.Equal("TestAssembly", component.AssemblyName);
|
||||||
|
Assert.Equal("Test.MyComponent", component.Name);
|
||||||
|
|
||||||
|
var attribute = Assert.Single(component.BoundAttributes);
|
||||||
|
Assert.Equal("OnClick", attribute.Name);
|
||||||
|
Assert.Equal(BlazorApi.UIEventHandler.FullTypeName, attribute.TypeName);
|
||||||
|
|
||||||
|
Assert.False(attribute.HasIndexer);
|
||||||
|
Assert.False(attribute.IsBooleanProperty);
|
||||||
|
Assert.False(attribute.IsEnum);
|
||||||
|
Assert.False(attribute.IsStringProperty);
|
||||||
|
Assert.True(attribute.IsUIEventHandlerProperty());
|
||||||
|
}
|
||||||
|
|
||||||
// For simplicity in testing, exlude the built-in components. We'll add more and we
|
// For simplicity in testing, exlude the built-in components. We'll add more and we
|
||||||
// don't want to update the tests when that happens.
|
// don't want to update the tests when that happens.
|
||||||
private TagHelperDescriptor[] ExcludeBuiltInComponents(TagHelperDescriptorProviderContext context)
|
private TagHelperDescriptor[] ExcludeBuiltInComponents(TagHelperDescriptorProviderContext context)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// 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 Microsoft.AspNetCore.Blazor.Components;
|
using Microsoft.AspNetCore.Blazor.Components;
|
||||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
@ -58,10 +59,21 @@ namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
||||||
Assert.Equal(attributeValue, frame.AttributeValue);
|
Assert.Equal(attributeValue, frame.AttributeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Attribute(RenderTreeFrame frame, string attributeName, Action<object> attributeValidator, int? sequence = null)
|
||||||
|
{
|
||||||
|
AssertFrame.Attribute(frame, attributeName, sequence);
|
||||||
|
attributeValidator(frame.AttributeValue);
|
||||||
|
}
|
||||||
|
|
||||||
public static void Component<T>(RenderTreeFrame frame, int? subtreeLength = null, int? sequence = null) where T : IComponent
|
public static void Component<T>(RenderTreeFrame frame, int? subtreeLength = null, int? sequence = null) where T : IComponent
|
||||||
|
{
|
||||||
|
Component(frame, typeof(T).FullName, subtreeLength, sequence);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Component(RenderTreeFrame frame, string typeName, int? subtreeLength = null, int? sequence = null)
|
||||||
{
|
{
|
||||||
Assert.Equal(RenderTreeFrameType.Component, frame.FrameType);
|
Assert.Equal(RenderTreeFrameType.Component, frame.FrameType);
|
||||||
Assert.Equal(typeof(T), frame.ComponentType);
|
Assert.Equal(typeName, frame.ComponentType.FullName);
|
||||||
if (subtreeLength.HasValue)
|
if (subtreeLength.HasValue)
|
||||||
{
|
{
|
||||||
Assert.Equal(subtreeLength.Value, frame.ComponentSubtreeLength);
|
Assert.Equal(subtreeLength.Value, frame.ComponentSubtreeLength);
|
||||||
|
|
|
||||||
|
|
@ -122,8 +122,10 @@
|
||||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj">
|
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj">
|
||||||
<Name>Microsoft.AspNetCore.Blazor.Razor.Extensions</Name>
|
<Name>Microsoft.AspNetCore.Blazor.Razor.Extensions</Name>
|
||||||
<Private>False</Private>
|
<Private>False</Private>
|
||||||
<IncludeOutputGroupsInVSIX></IncludeOutputGroupsInVSIX>
|
<IncludeOutputGroupsInVSIX>
|
||||||
<IncludeOutputGroupsInVSIXLocalOnly></IncludeOutputGroupsInVSIXLocalOnly>
|
</IncludeOutputGroupsInVSIX>
|
||||||
|
<IncludeOutputGroupsInVSIXLocalOnly>
|
||||||
|
</IncludeOutputGroupsInVSIXLocalOnly>
|
||||||
</ProjectReference>
|
</ProjectReference>
|
||||||
<Content Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\bin\$(Configuration)\net461\Microsoft.AspNetCore.Blazor.Razor.Extensions.dll">
|
<Content Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\bin\$(Configuration)\net461\Microsoft.AspNetCore.Blazor.Razor.Extensions.dll">
|
||||||
<Link>Microsoft.AspNetCore.Blazor.Razor.Extensions.dll</Link>
|
<Link>Microsoft.AspNetCore.Blazor.Razor.Extensions.dll</Link>
|
||||||
|
|
@ -146,7 +148,6 @@
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
<Visible>false</Visible>
|
<Visible>false</Visible>
|
||||||
</Content>
|
</Content>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
This is built as a P2P by Microsoft.VisualStudio.LanguageServices.Blazor. This is required, adding the P2P
|
This is built as a P2P by Microsoft.VisualStudio.LanguageServices.Blazor. This is required, adding the P2P
|
||||||
to this project will cause a NU1201 error that doesn't have a workaround.
|
to this project will cause a NU1201 error that doesn't have a workaround.
|
||||||
|
|
@ -194,23 +195,23 @@
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.CoreUtility" Version="15.0.26201" />
|
<PackageReference Include="Microsoft.VisualStudio.CoreUtility" Version="15.6.27413" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Imaging" Version="15.0.26201" />
|
<PackageReference Include="Microsoft.VisualStudio.Imaging" Version="15.6.27413" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.OLE.Interop" Version="7.10.6071" />
|
<PackageReference Include="Microsoft.VisualStudio.OLE.Interop" Version="7.10.6071" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.SDK.EmbedInteropTypes" Version="15.0.10" />
|
<PackageReference Include="Microsoft.VisualStudio.SDK.EmbedInteropTypes" Version="15.0.16" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.15.0" Version="15.0.26201" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.15.0" Version="15.6.27413" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Framework" Version="15.0.26201" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Framework" Version="15.6.27413" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop" Version="7.10.6071" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop" Version="7.10.6072" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.10.0" Version="10.0.30319" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.10.0" Version="10.0.30320" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.11.0" Version="11.0.61030" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.11.0" Version="11.0.61031" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.12.0" Version="12.0.30110" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.12.0" Version="12.0.30110" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.8.0" Version="8.0.50727" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.8.0" Version="8.0.50728" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.9.0" Version="9.0.30729" />
|
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.9.0" Version="9.0.30730" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop" Version="7.10.6070" />
|
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop" Version="7.10.6071" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop.8.0" Version="8.0.50727" />
|
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop.8.0" Version="8.0.50728" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.0.240" />
|
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.6.46" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Utilities" Version="15.0.26201" />
|
<PackageReference Include="Microsoft.VisualStudio.Utilities" Version="15.6.27413" />
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="15.0.82" />
|
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="15.3.15" />
|
||||||
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="15.5.100" />
|
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="15.5.100" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,12 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.Razor.Workspaces" Version="$(RazorPackageVersion)" />
|
<PackageReference Include="Microsoft.CodeAnalysis.Razor.Workspaces" Version="$(RazorPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.CoreUtility" Version="15.6.27413" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.LanguageServices.Razor" Version="$(RazorPackageVersion)" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Text.UI.Wpf" Version="15.6.27413" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop" Version="7.10.6071" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop.8.0" Version="8.0.50728" />
|
||||||
|
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.6.46" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel.Composition;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.CodeAnalysis.Razor;
|
||||||
|
using Microsoft.VisualStudio.Editor.Razor;
|
||||||
|
using Microsoft.VisualStudio.Text;
|
||||||
|
using Microsoft.VisualStudio.Text.Editor;
|
||||||
|
using Microsoft.VisualStudio.Utilities;
|
||||||
|
|
||||||
|
namespace Microsoft.VisualStudio.LanguageServices.Blazor
|
||||||
|
{
|
||||||
|
[ContentType(RazorLanguage.ContentType)]
|
||||||
|
[TextViewRole(PredefinedTextViewRoles.Editable)]
|
||||||
|
[Export(typeof(IWpfTextViewConnectionListener))]
|
||||||
|
internal class BlazorOpenDocumentTracker : IWpfTextViewConnectionListener
|
||||||
|
{
|
||||||
|
private readonly RazorEditorFactoryService _editorFactory;
|
||||||
|
private readonly Workspace _workspace;
|
||||||
|
|
||||||
|
private readonly HashSet<IWpfTextView> _openViews;
|
||||||
|
|
||||||
|
private Type _codeGeneratorType;
|
||||||
|
|
||||||
|
[ImportingConstructor]
|
||||||
|
public BlazorOpenDocumentTracker(
|
||||||
|
RazorEditorFactoryService editorFactory,
|
||||||
|
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
|
||||||
|
{
|
||||||
|
if (editorFactory == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(editorFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspace == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(workspace));
|
||||||
|
}
|
||||||
|
|
||||||
|
_editorFactory = editorFactory;
|
||||||
|
_workspace = workspace;
|
||||||
|
|
||||||
|
_openViews = new HashSet<IWpfTextView>();
|
||||||
|
|
||||||
|
_workspace.WorkspaceChanged += Workspace_WorkspaceChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Workspace Workspace => _workspace;
|
||||||
|
|
||||||
|
public void SubjectBuffersConnected(IWpfTextView textView, ConnectionReason reason, Collection<ITextBuffer> subjectBuffers)
|
||||||
|
{
|
||||||
|
if (textView == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(nameof(textView));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subjectBuffers == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(subjectBuffers));
|
||||||
|
}
|
||||||
|
|
||||||
|
_openViews.Add(textView);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SubjectBuffersDisconnected(IWpfTextView textView, ConnectionReason reason, Collection<ITextBuffer> subjectBuffers)
|
||||||
|
{
|
||||||
|
if (textView == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(nameof(textView));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subjectBuffers == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(subjectBuffers));
|
||||||
|
}
|
||||||
|
|
||||||
|
_openViews.Remove(textView);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're watching the Roslyn workspace for changes specifically because we want
|
||||||
|
// to know when the language service has processed a file change.
|
||||||
|
//
|
||||||
|
// It might be more elegant to use a file watcher rather than sniffing workspace events
|
||||||
|
// but there would be a delay between the file watcher and Roslyn processing the update.
|
||||||
|
private void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
|
||||||
|
{
|
||||||
|
switch (e.Kind)
|
||||||
|
{
|
||||||
|
case WorkspaceChangeKind.DocumentAdded:
|
||||||
|
case WorkspaceChangeKind.DocumentChanged:
|
||||||
|
case WorkspaceChangeKind.DocumentInfoChanged:
|
||||||
|
case WorkspaceChangeKind.DocumentReloaded:
|
||||||
|
case WorkspaceChangeKind.DocumentRemoved:
|
||||||
|
|
||||||
|
var document = e.NewSolution.GetDocument(e.DocumentId);
|
||||||
|
if (document == null || document.FilePath == null)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.FilePath.EndsWith(".g.i.cs"))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnDeclarationsChanged();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDeclarationsChanged()
|
||||||
|
{
|
||||||
|
// This is a design-time Razor file change.Go poke all of the open
|
||||||
|
// Razor documents and tell them to update.
|
||||||
|
var buffers = _openViews
|
||||||
|
.SelectMany(v => v.BufferGraph.GetTextBuffers(b => b.ContentType.IsOfType("RazorCSharp")))
|
||||||
|
.Distinct()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
if (_codeGeneratorType == null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var assembly = Assembly.Load("Microsoft.VisualStudio.Web.Editors.Razor.4_0, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
||||||
|
_codeGeneratorType = assembly.GetType("Microsoft.VisualStudio.Web.Editors.Razor.RazorCodeGenerator");
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// If this fails, just unsubscribe. We won't be able to do our work, so just don't waste time.
|
||||||
|
_workspace.WorkspaceChanged -= Workspace_WorkspaceChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var buffer in buffers)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var tryGetFromBuffer = _codeGeneratorType.GetMethod("TryGetFromBuffer", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
|
||||||
|
var args = new object[] { buffer, null };
|
||||||
|
if (!(bool)tryGetFromBuffer.Invoke(null, args) || args[1] == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var field = _codeGeneratorType.GetField("_tagHelperDescriptorResolver", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
var resolver = field.GetValue(args[1]);
|
||||||
|
if (resolver == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var reset = resolver.GetType().GetMethod("ResetTagHelperDescriptors", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||||||
|
if (reset == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
reset.Invoke(resolver, Array.Empty<object>());
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
// If this fails, just unsubscribe. We won't be able to do our work, so just don't waste time.
|
||||||
|
_workspace.WorkspaceChanged -= Workspace_WorkspaceChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue