Implement layout and implements with directives
- Remove haxxxx - Add proper directives with tooling support
This commit is contained in:
parent
57a04fb178
commit
a053155ab4
|
|
@ -1 +1 @@
|
|||
@(Layout<MainLayout>())
|
||||
@layout MainLayout
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@(Implements<ILayoutComponent>())
|
||||
@implements ILayoutComponent
|
||||
|
||||
<div class='container-fluid'>
|
||||
<div class='row'>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
public static readonly string BuildRenderTree = nameof(BuildRenderTree);
|
||||
}
|
||||
|
||||
public static class LayoutAttribute
|
||||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.Layouts.LayoutAttribute";
|
||||
}
|
||||
|
||||
public static class RenderFragment
|
||||
{
|
||||
public static readonly string FullTypeName = "Microsoft.AspNetCore.Blazor.RenderFragment";
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
}
|
||||
|
||||
FunctionsDirective.Register(builder);
|
||||
InjectDirective.Register(builder);
|
||||
ImplementsDirective.Register(builder);
|
||||
InheritsDirective.Register(builder);
|
||||
TemporaryLayoutPass.Register(builder);
|
||||
TemporaryImplementsPass.Register(builder);
|
||||
InjectDirective.Register(builder);
|
||||
LayoutDirective.Register(builder);
|
||||
|
||||
builder.Features.Remove(builder.Features.OfType<IImportProjectFeature>().Single());
|
||||
builder.Features.Add(new BlazorImportProjectFeature());
|
||||
|
|
@ -41,10 +41,10 @@ namespace Microsoft.AspNetCore.Blazor.Razor
|
|||
}
|
||||
|
||||
FunctionsDirective.Register(builder);
|
||||
InjectDirective.Register(builder);
|
||||
ImplementsDirective.Register(builder);
|
||||
InheritsDirective.Register(builder);
|
||||
TemporaryLayoutPass.Register(builder);
|
||||
TemporaryImplementsPass.Register(builder);
|
||||
InjectDirective.Register(builder);
|
||||
LayoutDirective.Register(builder);
|
||||
|
||||
builder.Features.Add(new ConfigureBlazorCodeGenerationOptions());
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 ImplementsDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
|
||||
"implements",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.AddTypeToken(Resources.ImplementsDirective_TypeToken_Name, Resources.ImplementsDirective_TypeToken_Description);
|
||||
builder.Usage = DirectiveUsage.FileScopedMultipleOccurring;
|
||||
builder.Description = Resources.ImplementsDirective_Description;
|
||||
});
|
||||
|
||||
public static void Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new ImplementsDirectivePass());
|
||||
}
|
||||
|
||||
public static void Register(IRazorEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new ImplementsDirectivePass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
// 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 Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Extensions;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
internal class ImplementsDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@class == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (@class.Interfaces == null)
|
||||
{
|
||||
@class.Interfaces = new List<string>();
|
||||
}
|
||||
|
||||
foreach (var implements in documentNode.FindDirectiveReferences(ImplementsDirective.Directive))
|
||||
{
|
||||
var token = ((DirectiveIntermediateNode)implements.Node).Tokens.FirstOrDefault();
|
||||
if (token != null)
|
||||
{
|
||||
@class.Interfaces.Add(token.Content);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// 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 LayoutDirective
|
||||
{
|
||||
public static readonly DirectiveDescriptor Directive = DirectiveDescriptor.CreateDirective(
|
||||
"layout",
|
||||
DirectiveKind.SingleLine,
|
||||
builder =>
|
||||
{
|
||||
builder.AddTypeToken(Resources.LayoutDirective_TypeToken_Name, Resources.LayoutDirective_TypeToken_Description);
|
||||
builder.Usage = DirectiveUsage.FileScopedSinglyOccurring;
|
||||
builder.Description = Resources.LayoutDirective_Description;
|
||||
});
|
||||
|
||||
public static void Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new LayoutDirectivePass());
|
||||
}
|
||||
|
||||
public static void Register(IRazorEngineBuilder builder)
|
||||
{
|
||||
if (builder == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(builder));
|
||||
}
|
||||
|
||||
builder.AddDirective(Directive);
|
||||
builder.Features.Add(new LayoutDirectivePass());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
// 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.Linq;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
internal class LayoutDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
var @namespace = documentNode.FindPrimaryNamespace();
|
||||
var @class = documentNode.FindPrimaryClass();
|
||||
if (@namespace == null || @class == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var directives = documentNode.FindDirectiveReferences(LayoutDirective.Directive);
|
||||
if (directives.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var token = ((DirectiveIntermediateNode)directives[0].Node).Tokens.FirstOrDefault();
|
||||
if (token == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var attributeNode = new CSharpCodeIntermediateNode();
|
||||
attributeNode.Children.Add(new IntermediateToken()
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $"[{BlazorApi.LayoutAttribute.FullTypeName}(typeof({token.Content}))]" + Environment.NewLine,
|
||||
});
|
||||
|
||||
// Insert the new attribute on top of the class
|
||||
for (var i = 0; i < @namespace.Children.Count; i++)
|
||||
{
|
||||
if (object.ReferenceEquals(@namespace.Children[i], @class))
|
||||
{
|
||||
@namespace.Children.Insert(i, attributeNode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -23,4 +23,19 @@
|
|||
</ProjectReference>
|
||||
<Reference Include="..\anglesharp\AngleSharpBuilder\dist\Microsoft.AspNetCore.Blazor.AngleSharp.dll" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Update="Resources.Designer.cs">
|
||||
<DesignTime>True</DesignTime>
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Update="Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,117 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor {
|
||||
using System;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "15.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources {
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources() {
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager {
|
||||
get {
|
||||
if (object.ReferenceEquals(resourceMan, null)) {
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.AspNetCore.Blazor.Razor.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture {
|
||||
get {
|
||||
return resourceCulture;
|
||||
}
|
||||
set {
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Declares an interface implementation for the current document..
|
||||
/// </summary>
|
||||
internal static string ImplementsDirective_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("ImplementsDirective_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The interface type implemented by the current document..
|
||||
/// </summary>
|
||||
internal static string ImplementsDirective_TypeToken_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("ImplementsDirective_TypeToken_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to TypeName.
|
||||
/// </summary>
|
||||
internal static string ImplementsDirective_TypeToken_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("ImplementsDirective_TypeToken_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Declares a layout type for the current document..
|
||||
/// </summary>
|
||||
internal static string LayoutDirective_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("LayoutDirective_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to The interface type implemented by the current document..
|
||||
/// </summary>
|
||||
internal static string LayoutDirective_TypeToken_Description {
|
||||
get {
|
||||
return ResourceManager.GetString("LayoutDirective_TypeToken_Description", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to TypeName.
|
||||
/// </summary>
|
||||
internal static string LayoutDirective_TypeToken_Name {
|
||||
get {
|
||||
return ResourceManager.GetString("LayoutDirective_TypeToken_Name", resourceCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="ImplementsDirective_Description" xml:space="preserve">
|
||||
<value>Declares an interface implementation for the current document.</value>
|
||||
</data>
|
||||
<data name="ImplementsDirective_TypeToken_Description" xml:space="preserve">
|
||||
<value>The interface type implemented by the current document.</value>
|
||||
</data>
|
||||
<data name="ImplementsDirective_TypeToken_Name" xml:space="preserve">
|
||||
<value>TypeName</value>
|
||||
</data>
|
||||
<data name="LayoutDirective_Description" xml:space="preserve">
|
||||
<value>Declares a layout type for the current document.</value>
|
||||
</data>
|
||||
<data name="LayoutDirective_TypeToken_Description" xml:space="preserve">
|
||||
<value>The interface type implemented by the current document.</value>
|
||||
</data>
|
||||
<data name="LayoutDirective_TypeToken_Name" xml:space="preserve">
|
||||
<value>TypeName</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
// 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.Razor.Language;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
// This only exists to support SourceLinesVisitor and can be removed once
|
||||
// we are able to implement proper Blazor-specific directives
|
||||
internal class SourceLinesEnumerable : IEnumerable<string>
|
||||
{
|
||||
private RazorSourceDocument _source;
|
||||
|
||||
public SourceLinesEnumerable(RazorSourceDocument source)
|
||||
=> _source = source;
|
||||
|
||||
public IEnumerator<string> GetEnumerator()
|
||||
=> new SourceLinesEnumerator(_source);
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> new SourceLinesEnumerator(_source);
|
||||
|
||||
private class SourceLinesEnumerator : IEnumerator<string>
|
||||
{
|
||||
private readonly RazorSourceDocument _sourceDocument;
|
||||
private readonly RazorSourceLineCollection _lines;
|
||||
private int _currentLineIndex;
|
||||
private int _cumulativeLengthOfPrecedingLines;
|
||||
private char[] _currentLineBuffer = new char[200]; // Grows if needed
|
||||
private string _currentLineText;
|
||||
|
||||
public SourceLinesEnumerator(RazorSourceDocument sourceDocument)
|
||||
{
|
||||
_sourceDocument = sourceDocument ?? throw new ArgumentNullException(nameof(sourceDocument));
|
||||
_lines = _sourceDocument.Lines;
|
||||
_currentLineIndex = -1;
|
||||
}
|
||||
|
||||
public string Current => _currentLineText;
|
||||
|
||||
object IEnumerator.Current => _currentLineText;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
_currentLineIndex++;
|
||||
if (_currentLineIndex >= _lines.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var lineLength = _lines.GetLineLength(_currentLineIndex);
|
||||
if (_currentLineBuffer.Length < lineLength)
|
||||
{
|
||||
_currentLineBuffer = new char[lineLength];
|
||||
}
|
||||
|
||||
_sourceDocument.CopyTo(_cumulativeLengthOfPrecedingLines, _currentLineBuffer, 0, lineLength);
|
||||
_currentLineText = new string(_currentLineBuffer, 0, lineLength);
|
||||
_cumulativeLengthOfPrecedingLines += lineLength;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_currentLineIndex = -1;
|
||||
_cumulativeLengthOfPrecedingLines = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
// 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.Razor.Language;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
// This only exists to support the temporary fake Blazor directives and can
|
||||
// be removed once we are able to implement proper Blazor-specific directives
|
||||
internal abstract class SourceLinesVisitor
|
||||
{
|
||||
/// <summary>
|
||||
/// Visits each line in the document's imports (in order), followed by
|
||||
/// each line in the document's primary syntax tree.
|
||||
/// </summary>
|
||||
public void Visit(RazorCodeDocument codeDocument)
|
||||
{
|
||||
foreach (var import in codeDocument.GetImportSyntaxTrees())
|
||||
{
|
||||
VisitSyntaxTree(import);
|
||||
}
|
||||
|
||||
VisitSyntaxTree(codeDocument.GetSyntaxTree());
|
||||
}
|
||||
|
||||
protected abstract void VisitLine(string line);
|
||||
|
||||
private void VisitSyntaxTree(RazorSyntaxTree syntaxTree)
|
||||
{
|
||||
var sourceDocument = syntaxTree.Source;
|
||||
foreach (var line in new SourceLinesEnumerable(sourceDocument))
|
||||
{
|
||||
VisitLine(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
// 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.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
// Until we're able to add real directives, this implements a temporary mechanism whereby we
|
||||
// search for lines of the form "@({regex})" (including in the imports sources) and do something
|
||||
// with the regex matches. Also we remove the corresponding tokens from the intermediate
|
||||
// representation to stop them from interfering with the compiled output on its own.
|
||||
internal abstract class TemporaryFakeDirectivePass : IntermediateNodePassBase, IRazorDirectiveClassifierPass
|
||||
{
|
||||
private readonly Regex _sourceLineRegex;
|
||||
private readonly Regex _tokenRegex;
|
||||
|
||||
protected TemporaryFakeDirectivePass(string syntaxRegexPattern)
|
||||
{
|
||||
_sourceLineRegex = new Regex($@"^\s*@\({syntaxRegexPattern}\)\s*$");
|
||||
_tokenRegex = new Regex($@"^{syntaxRegexPattern}$");
|
||||
}
|
||||
|
||||
protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
|
||||
{
|
||||
// First, remove any matching lines from the intermediate representation
|
||||
// in the primary document. Don't need to remove them from imports as they
|
||||
// have no effect there anyway.
|
||||
var methodNode = documentNode.FindPrimaryMethod();
|
||||
var methodNodeChildren = methodNode.Children.ToList();
|
||||
foreach (var node in methodNodeChildren)
|
||||
{
|
||||
if (IsMatchingNode(node))
|
||||
{
|
||||
methodNode.Children.Remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Now find the matching lines in the source code (including imports)
|
||||
// Need to do this on source, because the imports aren't in the intermediate representation
|
||||
var linesVisitor = new RegexSourceLinesVisitor(_sourceLineRegex);
|
||||
linesVisitor.Visit(codeDocument);
|
||||
if (linesVisitor.MatchedContent.Any())
|
||||
{
|
||||
HandleMatchedContent(codeDocument, linesVisitor.MatchedContent);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void HandleMatchedContent(RazorCodeDocument codeDocument, IEnumerable<string> matchedContent);
|
||||
|
||||
private bool IsMatchingNode(IntermediateNode node)
|
||||
=> node.Children.Count == 1
|
||||
&& node.Children[0] is IntermediateToken intermediateToken
|
||||
&& _tokenRegex.IsMatch(intermediateToken.Content);
|
||||
|
||||
private class RegexSourceLinesVisitor : SourceLinesVisitor
|
||||
{
|
||||
private Regex _searchRegex;
|
||||
private readonly List<string> _matchedContent = new List<string>();
|
||||
|
||||
public IEnumerable<string> MatchedContent => _matchedContent;
|
||||
|
||||
public RegexSourceLinesVisitor(Regex searchRegex)
|
||||
{
|
||||
_searchRegex = searchRegex;
|
||||
}
|
||||
|
||||
protected override void VisitLine(string line)
|
||||
{
|
||||
// Pick the most specific by looking for the final one in the sources
|
||||
var match = _searchRegex.Match(line);
|
||||
if (match.Success)
|
||||
{
|
||||
_matchedContent.Add(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
// 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.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// This code is temporary. It finds top-level expressions of the form
|
||||
/// @Implements<SomeInterfaceType>()
|
||||
/// ... and converts them into interface declarations on the class.
|
||||
/// Once we're able to add Blazor-specific directives and have them show up in tooling,
|
||||
/// we'll replace this with a simpler and cleaner "@implements SomeInterfaceType" directive.
|
||||
/// </summary>
|
||||
internal class TemporaryImplementsPass : TemporaryFakeDirectivePass
|
||||
{
|
||||
// Example: "Implements<MyApp.Namespace.ISomeType<T1, T2>>()"
|
||||
// Captures: MyApp.Namespace.ISomeType<T1, T2>
|
||||
private const string ImplementsTokenPattern = @"\s*Implements\s*<(.+)\>\s*\(\s*\)\s*";
|
||||
|
||||
public static void Register(IRazorEngineBuilder builder)
|
||||
{
|
||||
builder.Features.Add(new TemporaryImplementsPass());
|
||||
}
|
||||
|
||||
public static void Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
builder.Features.Add(new TemporaryImplementsPass());
|
||||
}
|
||||
|
||||
private TemporaryImplementsPass() : base(ImplementsTokenPattern)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void HandleMatchedContent(RazorCodeDocument codeDocument, IEnumerable<string> matchedContent)
|
||||
{
|
||||
var classNode = codeDocument.GetDocumentIntermediateNode().FindPrimaryClass();
|
||||
if (classNode.Interfaces == null)
|
||||
{
|
||||
classNode.Interfaces = new List<string>();
|
||||
}
|
||||
|
||||
foreach (var implementsType in matchedContent)
|
||||
{
|
||||
classNode.Interfaces.Add(implementsType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
// 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.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.Language.Intermediate;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.AspNetCore.Blazor.Razor
|
||||
{
|
||||
/// <summary>
|
||||
/// This code is temporary. It finds source code lines of the form
|
||||
/// @Layout<SomeType>()
|
||||
/// ... and converts them into [Layout(typeof(SomeType))] attributes on the class.
|
||||
/// Once we're able to add Blazor-specific directives and have them show up in tooling,
|
||||
/// we'll replace this with a simpler and cleaner "@Layout SomeType" directive.
|
||||
/// </summary>
|
||||
internal class TemporaryLayoutPass : TemporaryFakeDirectivePass
|
||||
{
|
||||
// Example: "Layout<MyApp.Namespace.SomeType<T1, T2>>()"
|
||||
// Captures: MyApp.Namespace.SomeType<T1, T2>
|
||||
private const string LayoutTokenPattern = @"\s*Layout\s*<(.+)\>\s*\(\s*\)\s*";
|
||||
|
||||
private const string LayoutAttributeTypeName
|
||||
= "Microsoft.AspNetCore.Blazor.Layouts.LayoutAttribute";
|
||||
|
||||
public static void Register(IRazorEngineBuilder builder)
|
||||
{
|
||||
builder.Features.Add(new TemporaryLayoutPass());
|
||||
}
|
||||
|
||||
public static void Register(RazorProjectEngineBuilder builder)
|
||||
{
|
||||
builder.Features.Add(new TemporaryLayoutPass());
|
||||
}
|
||||
|
||||
private TemporaryLayoutPass() : base(LayoutTokenPattern)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void HandleMatchedContent(RazorCodeDocument codeDocument, IEnumerable<string> matchedContent)
|
||||
{
|
||||
var chosenLayoutType = matchedContent.Last();
|
||||
var attributeNode = new CSharpCodeIntermediateNode();
|
||||
attributeNode.Children.Add(new IntermediateToken()
|
||||
{
|
||||
Kind = TokenKind.CSharp,
|
||||
Content = $"[{LayoutAttributeTypeName}(typeof ({chosenLayoutType}))]" + Environment.NewLine,
|
||||
});
|
||||
|
||||
var docNode = codeDocument.GetDocumentIntermediateNode();
|
||||
var namespaceNode = docNode.FindPrimaryNamespace();
|
||||
var classNode = docNode.FindPrimaryClass();
|
||||
var classNodeIndex = namespaceNode
|
||||
.Children
|
||||
.IndexOf(classNode);
|
||||
namespaceNode.Children.Insert(classNodeIndex, attributeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -663,12 +663,12 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsLayoutDeclarationsViaTemporarySyntax()
|
||||
public void SupportsLayoutDeclarations()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testComponentTypeName = FullTypeName<TestLayout>();
|
||||
var component = CompileToComponent(
|
||||
$"@(Layout<{testComponentTypeName}>())\n" +
|
||||
$"@layout {testComponentTypeName}\n" +
|
||||
$"Hello");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
|
|
@ -677,23 +677,23 @@ namespace Microsoft.AspNetCore.Blazor.Build.Test
|
|||
Assert.NotNull(layoutAttribute);
|
||||
Assert.Equal(typeof(TestLayout), layoutAttribute.LayoutType);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "\nHello"));
|
||||
frame => AssertFrame.Text(frame, "Hello"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupportsImplementsDeclarationsViaTemporarySyntax()
|
||||
public void SupportsImplementsDeclarations()
|
||||
{
|
||||
// Arrange/Act
|
||||
var testInterfaceTypeName = FullTypeName<ITestInterface>();
|
||||
var component = CompileToComponent(
|
||||
$"@(Implements<{testInterfaceTypeName}>())\n" +
|
||||
$"@implements {testInterfaceTypeName}\n" +
|
||||
$"Hello");
|
||||
var frames = GetRenderTree(component);
|
||||
|
||||
// Assert
|
||||
Assert.IsAssignableFrom<ITestInterface>(component);
|
||||
Assert.Collection(frames,
|
||||
frame => AssertFrame.Text(frame, "\nHello"));
|
||||
frame => AssertFrame.Text(frame, "Hello"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
Loading…
Reference in New Issue