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>
|
||||
|
||||
<!-- 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>
|
||||
|
|
@ -294,6 +294,8 @@
|
|||
Outputs="@(_BlazorTempAssembly);$(NonExistentFile)"
|
||||
Condition="'$(DesignTimeBuild)'!='true'">
|
||||
|
||||
<MakeDir Directories="%(_BlazorTempAssembly.RelativeDir)" />
|
||||
|
||||
<!-- 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,
|
||||
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 CloseComponent = nameof(CloseElement);
|
||||
public static readonly string CloseComponent = nameof(CloseComponent);
|
||||
|
||||
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 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
|
||||
{
|
||||
/// <summary>
|
||||
/// Directs a <see cref="DocumentWriter"/> to use <see cref="BlazorIntermediateNodeWriter"/>.
|
||||
/// Directs a <see cref="DocumentWriter"/> to use <see cref="BlazorRuntimeNodeWriter"/>.
|
||||
/// </summary>
|
||||
internal class BlazorCodeTarget : CodeTarget
|
||||
{
|
||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
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>()
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
// 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 Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
|
|
@ -51,6 +54,40 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
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(
|
||||
SourceSpan? razorTokenPosition,
|
||||
TextPosition htmlNodePosition)
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
builder.Features.Add(new ConfigureBlazorCodeGenerationOptions());
|
||||
|
||||
builder.Features.Add(new ComponentDocumentClassifierPass());
|
||||
builder.Features.Add(new ComponentLoweringPass());
|
||||
|
||||
builder.Features.Add(new ComponentTagHelperDescriptorProvider());
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
{
|
||||
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; }
|
||||
|
||||
|
|
@ -27,9 +32,23 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
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.
|
||||
imports.AddRange(GetHierarchicalImports(ProjectEngine.FileSystem, projectItem));
|
||||
|
||||
|
|
@ -45,22 +64,16 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
private class VirtualProjectItem : RazorProjectItem
|
||||
{
|
||||
private readonly byte[] _defaultImportBytes;
|
||||
private readonly byte[] _bytes;
|
||||
|
||||
private VirtualProjectItem()
|
||||
public VirtualProjectItem(string content)
|
||||
{
|
||||
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);
|
||||
|
||||
_defaultImportBytes = new byte[preamble.Length + contentBytes.Length];
|
||||
preamble.CopyTo(_defaultImportBytes, 0);
|
||||
contentBytes.CopyTo(_defaultImportBytes, preamble.Length);
|
||||
_bytes = new byte[preamble.Length + contentBytes.Length];
|
||||
preamble.CopyTo(_bytes, 0);
|
||||
contentBytes.CopyTo(_bytes, preamble.Length);
|
||||
}
|
||||
|
||||
public override string BasePath => null;
|
||||
|
|
@ -71,9 +84,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
|
||||
public override bool Exists => true;
|
||||
|
||||
public static VirtualProjectItem Instance { get; } = new VirtualProjectItem();
|
||||
|
||||
public override Stream Read() => new MemoryStream(_defaultImportBytes);
|
||||
public override Stream Read() => new MemoryStream(_bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
/// Generates the C# code corresponding to Razor source document contents.
|
||||
/// </summary>
|
||||
internal class BlazorIntermediateNodeWriter : IntermediateNodeWriter
|
||||
internal class BlazorRuntimeNodeWriter : BlazorNodeWriter
|
||||
{
|
||||
// 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)
|
||||
|
|
@ -48,16 +48,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
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)
|
||||
{
|
||||
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(
|
||||
SourceSpan? razorTokenPosition,
|
||||
TextPosition htmlNodePosition)
|
||||
|
|
@ -434,7 +591,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
codeWriter.WriteEndMethodInvocation();
|
||||
}
|
||||
|
||||
public void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
||||
public override void BeginWriteAttribute(CodeWriter codeWriter, string key)
|
||||
{
|
||||
codeWriter
|
||||
.WriteStartMethodInvocation($"{_scopeStack.BuilderVarName}.{nameof(BlazorApi.RenderTreeBuilder.AddAttribute)}")
|
||||
|
|
@ -444,11 +601,6 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
.WriteParameterSeparator();
|
||||
}
|
||||
|
||||
public override void WriteUsingDirective(CodeRenderingContext context, UsingDirectiveIntermediateNode node)
|
||||
{
|
||||
context.CodeWriter.WriteUsing(node.Content, endLine: true);
|
||||
}
|
||||
|
||||
private static string GetContent(HtmlContentIntermediateNode node)
|
||||
{
|
||||
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
|
||||
{
|
||||
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 =
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)
|
||||
|
|
@ -109,6 +111,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
pb.IsEnum = true;
|
||||
}
|
||||
|
||||
if (property.kind == PropertyKind.Delegate)
|
||||
{
|
||||
pb.Metadata.Add(UIEventHandlerPropertyMetadata, bool.TrueString);
|
||||
}
|
||||
|
||||
xml = property.property.GetDocumentationCommentXml();
|
||||
if (!string.IsNullOrEmpty(xml))
|
||||
{
|
||||
|
|
@ -145,13 +152,13 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
continue;
|
||||
}
|
||||
|
||||
var kind = PropertyKind.Default;
|
||||
if (properties.ContainsKey(property.Name))
|
||||
{
|
||||
// Not visible
|
||||
kind = PropertyKind.Ignored;
|
||||
continue;
|
||||
}
|
||||
|
||||
var kind = PropertyKind.Default;
|
||||
if (property.Parameters.Length != 0)
|
||||
{
|
||||
// Indexer
|
||||
|
|
@ -175,6 +182,15 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
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));
|
||||
}
|
||||
|
||||
|
|
@ -190,6 +206,7 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
Ignored,
|
||||
Default,
|
||||
Enum,
|
||||
Delegate,
|
||||
}
|
||||
|
||||
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,
|
||||
// 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);
|
||||
OffsetBuilderVarNumber(1);
|
||||
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.
|
||||
// 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.Runtime.InteropServices;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.RenderTree
|
||||
{
|
||||
|
|
@ -224,5 +224,34 @@ namespace Microsoft.AspNetCore.Blazor.RenderTree
|
|||
|
||||
internal RenderTreeFrame WithRegionSubtreeLength(int 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.
|
||||
// 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.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
|
|
@ -18,7 +21,7 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
using namespace Test
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
|
|
@ -34,6 +37,29 @@ using namespace Test
|
|||
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]
|
||||
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.
|
||||
|
||||
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.Test.Helpers;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Build.Test
|
||||
{
|
||||
public class ComponentRenderingRazorIntegrationTest : RazorIntegrationTestBase
|
||||
{
|
||||
internal override bool UseTwoPhaseCompilation => true;
|
||||
|
||||
[Fact]
|
||||
public void SupportsChildComponentsViaTemporarySyntax()
|
||||
public void Render_ChildComponent_Simple()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
||||
var component = CompileToComponent($"<c:{testComponentTypeName} />");
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent : BlazorComponent
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<MyComponent/>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 1, 0));
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 1, 0));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanPassParametersToComponents()
|
||||
public void Render_ChildComponent_WithParameters()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
||||
var testObjectTypeName = FullTypeName<SomeType>();
|
||||
// TODO: Once we have the improved component tooling and can allow syntax
|
||||
// like StringProperty="My string" or BoolProperty=true, update this
|
||||
// test to use that syntax.
|
||||
var component = CompileToComponent($"<c:{testComponentTypeName}" +
|
||||
$" IntProperty=@(123)" +
|
||||
$" BoolProperty=@true" +
|
||||
$" StringProperty=@(\"My string\")" +
|
||||
$" ObjectProperty=@(new {testObjectTypeName}()) />");
|
||||
// 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; }
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
var component = CompileToComponent(@"
|
||||
@addTagHelper *, TestAssembly
|
||||
<MyComponent
|
||||
IntProperty=""123""
|
||||
BoolProperty=""true""
|
||||
StringProperty=""My string""
|
||||
ObjectProperty=""new SomeType()""/>");
|
||||
|
||||
// Act
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 5, 0),
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 5, 0),
|
||||
frame => AssertFrame.Attribute(frame, "IntProperty", 123, 1),
|
||||
frame => AssertFrame.Attribute(frame, "BoolProperty", true, 2),
|
||||
frame => AssertFrame.Attribute(frame, "StringProperty", "My string", 3),
|
||||
frame =>
|
||||
{
|
||||
AssertFrame.Attribute(frame, "ObjectProperty", 4);
|
||||
Assert.IsType<SomeType>(frame.AttributeValue);
|
||||
Assert.Equal("Test.SomeType", frame.AttributeValue.GetType().FullName);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanIncludeChildrenInComponents()
|
||||
public void Render_ChildComponent_WithExplicitStringParameter()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
||||
var component = CompileToComponent($"<c:{testComponentTypeName} MyAttr=@(\"abc\")>" +
|
||||
$"Some text" +
|
||||
$"<some-child a='1'>Nested text</some-child>" +
|
||||
$"</c:{testComponentTypeName}>");
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
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);
|
||||
|
||||
// Assert: component frames are correct
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 3, 0),
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 3, 0),
|
||||
frame => AssertFrame.Attribute(frame, "MyAttr", "abc", 1),
|
||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 2));
|
||||
|
||||
// Assert: Captured ChildContent frames are correct
|
||||
var childFrames = GetFrames((RenderFragment)frames[2].AttributeValue);
|
||||
Assert.Collection(childFrames,
|
||||
Assert.Collection(
|
||||
childFrames,
|
||||
frame => AssertFrame.Text(frame, "Some text", 3),
|
||||
frame => AssertFrame.Element(frame, "some-child", 3, 4),
|
||||
frame => AssertFrame.Attribute(frame, "a", "1", 5),
|
||||
|
|
@ -82,21 +288,33 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void CanNestComponentChildContent()
|
||||
public void Render_ChildComponent_Nested()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testComponentTypeName = FullTypeName<TestComponent>();
|
||||
var component = CompileToComponent(
|
||||
$"<c:{testComponentTypeName}>" +
|
||||
$"<c:{testComponentTypeName}>" +
|
||||
$"Some text" +
|
||||
$"</c:{testComponentTypeName}>" +
|
||||
$"</c:{testComponentTypeName}>");
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(CSharpSyntaxTree.ParseText(@"
|
||||
using Microsoft.AspNetCore.Blazor;
|
||||
using Microsoft.AspNetCore.Blazor.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
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);
|
||||
|
||||
// Assert: outer component frames are correct
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 2, 0),
|
||||
Assert.Collection(
|
||||
frames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 0),
|
||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 1));
|
||||
|
||||
// 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
|
||||
// level, but we could change that part of the implementation if we wanted.
|
||||
var innerFrames = GetFrames((RenderFragment)frames[1].AttributeValue).ToArray();
|
||||
Assert.Collection(innerFrames,
|
||||
frame => AssertFrame.Component<TestComponent>(frame, 2, 2),
|
||||
Assert.Collection(
|
||||
innerFrames,
|
||||
frame => AssertFrame.Component(frame, "Test.MyComponent", 2, 2),
|
||||
frame => AssertFrame.Attribute(frame, RenderTreeBuilder.ChildContent, 3));
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
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 Microsoft.AspNetCore.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.Razor;
|
||||
using Microsoft.AspNetCore.Blazor.Test.Helpers;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
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()
|
||||
{
|
||||
AdditionalSyntaxTrees = new List<SyntaxTree>();
|
||||
AdditionalRazorItems = new List<RazorProjectItem>();
|
||||
|
||||
Configuration = BlazorExtensionInitializer.DefaultConfiguration;
|
||||
FileSystem = new VirtualRazorProjectFileSystem();
|
||||
WorkingDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? ArbitraryWindowsPath : ArbitraryMacLinuxPath;
|
||||
|
||||
DefaultBaseNamespace = "Test"; // Matches the default working directory
|
||||
DefaultFileName = "TestComponent.cshtml";
|
||||
|
||||
}
|
||||
|
||||
internal List<RazorProjectItem> AdditionalRazorItems { get; }
|
||||
|
||||
internal List<SyntaxTree> AdditionalSyntaxTrees { get; }
|
||||
|
||||
internal virtual RazorConfiguration Configuration { get; }
|
||||
|
|
@ -71,6 +76,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
internal virtual string DefaultBaseNamespace { get; }
|
||||
|
||||
internal virtual string DefaultFileName { get; }
|
||||
|
||||
internal virtual bool DesignTime { get; }
|
||||
|
||||
internal virtual VirtualRazorProjectFileSystem FileSystem { get; }
|
||||
|
||||
|
|
@ -82,6 +89,9 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
{
|
||||
return RazorProjectEngine.Create(configuration, FileSystem, b =>
|
||||
{
|
||||
// Turn off checksums, we're testing code generation.
|
||||
b.Features.Add(new SuppressChecksum());
|
||||
|
||||
BlazorExtensionInitializer.Register(b);
|
||||
|
||||
b.Features.Add(new CompilationTagHelperFeature());
|
||||
|
|
@ -92,58 +102,83 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
});
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(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)
|
||||
internal RazorProjectItem CreateProjectItem(string chtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
// FilePaths in Razor are **always** are of the form '/a/b/c.cshtml'
|
||||
var filePath = cshtmlRelativePath.Replace('\\', '/');
|
||||
var filePath = chtmlRelativePath.Replace('\\', '/');
|
||||
if (!filePath.StartsWith('/'))
|
||||
{
|
||||
filePath = '/' + filePath;
|
||||
}
|
||||
|
||||
var projectItem = new VirtualProjectItem(
|
||||
cshtmlRootPath,
|
||||
filePath,
|
||||
Path.Combine(cshtmlRootPath, cshtmlRelativePath),
|
||||
cshtmlRelativePath,
|
||||
Encoding.UTF8.GetBytes(cshtmlContent));
|
||||
return new VirtualProjectItem(
|
||||
WorkingDirectory,
|
||||
filePath,
|
||||
Path.Combine(WorkingDirectory, chtmlRelativePath),
|
||||
chtmlRelativePath,
|
||||
Encoding.UTF8.GetBytes(cshtmlContent.TrimStart()));
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlContent)
|
||||
{
|
||||
return CompileToCSharp(DefaultFileName, cshtmlContent);
|
||||
}
|
||||
|
||||
protected CompileToCSharpResult CompileToCSharp(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
if (UseTwoPhaseCompilation)
|
||||
{
|
||||
// The first phase won't include any metadata references for component discovery. This mirrors
|
||||
// what the build does.
|
||||
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
|
||||
var projectItem = CreateProjectItem(cshtmlRelativePath, cshtmlContent);
|
||||
codeDocument = projectEngine.Process(projectItem);
|
||||
var declaration = new CompileToCSharpResult
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
};
|
||||
|
||||
// Result of doing 'temp' compilation
|
||||
var tempAssembly = CompileToAssembly(declaration, BaseCompilation);
|
||||
var tempAssembly = CompileToAssembly(declaration);
|
||||
|
||||
// Add the 'temp' compilation as a metadata reference
|
||||
var references = BaseCompilation.References.Concat(new[] { tempAssembly.Compilation.ToMetadataReference() }).ToArray();
|
||||
projectEngine = CreateProjectEngine(BlazorExtensionInitializer.DefaultConfiguration, references);
|
||||
|
||||
// Result of real code
|
||||
codeDocument = projectEngine.Process(projectItem);
|
||||
|
||||
// Now update the any additional files
|
||||
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
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
|
|
@ -155,9 +190,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
// This will include the built-in Blazor components.
|
||||
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
|
||||
{
|
||||
BaseCompilation = BaseCompilation.AddSyntaxTrees(AdditionalSyntaxTrees),
|
||||
CodeDocument = codeDocument,
|
||||
Code = codeDocument.GetCSharpDocument().GeneratedCode,
|
||||
Diagnostics = codeDocument.GetCSharpDocument().Diagnostics,
|
||||
|
|
@ -167,19 +204,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
|
||||
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
return CompileToAssembly(WorkingDirectory, cshtmlRelativePath, cshtmlContent);
|
||||
}
|
||||
|
||||
protected CompileToAssemblyResult CompileToAssembly(string cshtmlRootDirectory, string cshtmlRelativePath, string cshtmlContent)
|
||||
{
|
||||
var cSharpResult = CompileToCSharp(cshtmlRootDirectory, cshtmlRelativePath, cshtmlContent);
|
||||
var cSharpResult = CompileToCSharp(cshtmlRelativePath, cshtmlContent);
|
||||
return CompileToAssembly(cSharpResult);
|
||||
}
|
||||
|
||||
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult, CSharpCompilation baseCompilation = null)
|
||||
protected CompileToAssemblyResult CompileToAssembly(CompileToCSharpResult cSharpResult)
|
||||
{
|
||||
baseCompilation = baseCompilation ?? BaseCompilation;
|
||||
|
||||
if (cSharpResult.Diagnostics.Any())
|
||||
{
|
||||
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[]
|
||||
{
|
||||
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())
|
||||
{
|
||||
compilation.Emit(peStream);
|
||||
|
||||
var diagnostics = compilation
|
||||
.GetDiagnostics()
|
||||
.Where(d => d.Severity != DiagnosticSeverity.Hidden);
|
||||
return new CompileToAssemblyResult
|
||||
{
|
||||
Compilation = compilation,
|
||||
|
|
@ -206,11 +243,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
Assembly = diagnostics.Any() ? null : Assembly.Load(peStream.ToArray())
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected IComponent CompileToComponent(string cshtmlSource)
|
||||
{
|
||||
var assemblyResult = CompileToAssembly(WorkingDirectory, DefaultFileName, cshtmlSource);
|
||||
var assemblyResult = CompileToAssembly(DefaultFileName, cshtmlSource);
|
||||
|
||||
var componentFullTypeName = $"{DefaultBaseNamespace}.{Path.GetFileNameWithoutExtension(DefaultFileName)}";
|
||||
return CompileToComponent(assemblyResult, componentFullTypeName);
|
||||
|
|
@ -223,8 +261,6 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
|
||||
protected IComponent CompileToComponent(CompileToAssemblyResult assemblyResult, string fullTypeName)
|
||||
{
|
||||
Assert.Empty(assemblyResult.Diagnostics);
|
||||
|
||||
var componentType = assemblyResult.Assembly.GetType(fullTypeName);
|
||||
if (componentType == null)
|
||||
{
|
||||
|
|
@ -255,6 +291,8 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
|
||||
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 string Code { get; set; }
|
||||
public IEnumerable<RazorDiagnostic> Diagnostics { get; set; }
|
||||
|
|
@ -284,5 +322,62 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
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
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Whitespace(frame, 0),
|
||||
frame => AssertFrame.Text(frame, "Hello", 1),
|
||||
frame => AssertFrame.Whitespace(frame, 2),
|
||||
frame => AssertFrame.Whitespace(frame, 3), // @((object)null)
|
||||
frame => AssertFrame.Whitespace(frame, 4),
|
||||
frame => AssertFrame.Text(frame, "123", 5),
|
||||
frame => AssertFrame.Whitespace(frame, 6),
|
||||
frame => AssertFrame.Text(frame, new object().ToString(), 7),
|
||||
frame => AssertFrame.Whitespace(frame, 8));
|
||||
frame => AssertFrame.Text(frame, "Hello", 0),
|
||||
frame => AssertFrame.Whitespace(frame, 1),
|
||||
frame => AssertFrame.Whitespace(frame, 2), // @((object)null)
|
||||
frame => AssertFrame.Whitespace(frame, 3),
|
||||
frame => AssertFrame.Text(frame, "123", 4),
|
||||
frame => AssertFrame.Whitespace(frame, 5),
|
||||
frame => AssertFrame.Text(frame, new object().ToString(), 6),
|
||||
frame => AssertFrame.Whitespace(frame, 7));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -70,12 +69,11 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
// Assert
|
||||
var frames = GetRenderTree(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Whitespace(frame, 0),
|
||||
frame => AssertFrame.Text(frame, "First", 1),
|
||||
frame => AssertFrame.Text(frame, "Second", 1),
|
||||
frame => AssertFrame.Text(frame, "Third", 1),
|
||||
frame => AssertFrame.Whitespace(frame, 2),
|
||||
frame => AssertFrame.Whitespace(frame, 3));
|
||||
frame => AssertFrame.Text(frame, "First", 0),
|
||||
frame => AssertFrame.Text(frame, "Second", 0),
|
||||
frame => AssertFrame.Text(frame, "Third", 0),
|
||||
frame => AssertFrame.Whitespace(frame, 1),
|
||||
frame => AssertFrame.Whitespace(frame, 2));
|
||||
}
|
||||
|
||||
[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);
|
||||
}
|
||||
|
||||
[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
|
||||
// don't want to update the tests when that happens.
|
||||
private TagHelperDescriptor[] ExcludeBuiltInComponents(TagHelperDescriptorProviderContext context)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
// 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.Blazor.Components;
|
||||
using Microsoft.AspNetCore.Blazor.RenderTree;
|
||||
using Xunit;
|
||||
|
|
@ -58,10 +59,21 @@ namespace Microsoft.AspNetCore.Blazor.Test.Helpers
|
|||
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
|
||||
{
|
||||
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(typeof(T), frame.ComponentType);
|
||||
Assert.Equal(typeName, frame.ComponentType.FullName);
|
||||
if (subtreeLength.HasValue)
|
||||
{
|
||||
Assert.Equal(subtreeLength.Value, frame.ComponentSubtreeLength);
|
||||
|
|
|
|||
|
|
@ -122,8 +122,10 @@
|
|||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Blazor.Razor.Extensions\Microsoft.AspNetCore.Blazor.Razor.Extensions.csproj">
|
||||
<Name>Microsoft.AspNetCore.Blazor.Razor.Extensions</Name>
|
||||
<Private>False</Private>
|
||||
<IncludeOutputGroupsInVSIX></IncludeOutputGroupsInVSIX>
|
||||
<IncludeOutputGroupsInVSIXLocalOnly></IncludeOutputGroupsInVSIXLocalOnly>
|
||||
<IncludeOutputGroupsInVSIX>
|
||||
</IncludeOutputGroupsInVSIX>
|
||||
<IncludeOutputGroupsInVSIXLocalOnly>
|
||||
</IncludeOutputGroupsInVSIXLocalOnly>
|
||||
</ProjectReference>
|
||||
<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>
|
||||
|
|
@ -146,7 +148,6 @@
|
|||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<Visible>false</Visible>
|
||||
</Content>
|
||||
|
||||
<!--
|
||||
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.
|
||||
|
|
@ -194,23 +195,23 @@
|
|||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.VisualStudio.CoreUtility" Version="15.0.26201" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Imaging" Version="15.0.26201" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.CoreUtility" Version="15.6.27413" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Imaging" Version="15.6.27413" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.OLE.Interop" Version="7.10.6071" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.SDK.EmbedInteropTypes" Version="15.0.10" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.15.0" Version="15.0.26201" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Framework" Version="15.0.26201" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop" Version="7.10.6071" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.10.0" Version="10.0.30319" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.11.0" Version="11.0.61030" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.SDK.EmbedInteropTypes" Version="15.0.16" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.15.0" Version="15.6.27413" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Framework" Version="15.6.27413" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop" Version="7.10.6072" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.10.0" Version="10.0.30320" />
|
||||
<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.8.0" Version="8.0.50727" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.9.0" Version="9.0.30729" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop" Version="7.10.6070" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.TextManager.Interop.8.0" Version="8.0.50727" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="15.0.240" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Utilities" Version="15.0.26201" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="15.0.82" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.8.0" Version="8.0.50728" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.9.0" Version="9.0.30730" />
|
||||
<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" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Utilities" Version="15.6.27413" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="15.3.15" />
|
||||
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="15.5.100" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,12 @@
|
|||
|
||||
<ItemGroup>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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