[Fixes #881] Added TagHelper IR support

This commit is contained in:
Ajay Bhargav Baaskaran 2016-12-13 20:51:24 -08:00
parent 61b2b0d4e7
commit e2dd09c918
30 changed files with 947 additions and 9 deletions

View File

@ -159,7 +159,6 @@ namespace Microsoft.AspNetCore.Razor.Evolution
contentLength,
sourceRangeStart.FilePath ?? _codeDocument.Source.Filename);
}
}
public override void VisitExpressionSpan(ExpressionChunkGenerator chunkGenerator, Span span)
@ -253,6 +252,145 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Builder.Pop();
}
public override void VisitStartTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
{
var tagHelperBlock = block as TagHelperBlock;
if (tagHelperBlock == null)
{
return;
}
DeclareTagHelperFields(tagHelperBlock);
Builder.Push(new TagHelperIRNode());
Builder.Push(new InitializeTagHelperStructureIRNode()
{
TagName = tagHelperBlock.TagName,
TagMode = tagHelperBlock.TagMode
});
}
public override void VisitEndTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
{
var tagHelperBlock = block as TagHelperBlock;
if (tagHelperBlock == null)
{
return;
}
Builder.Pop(); // Pop InitializeTagHelperStructureIRNode
AddTagHelperCreation(tagHelperBlock.Descriptors);
AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Descriptors);
AddExecuteTagHelpers();
Builder.Pop(); // Pop TagHelperIRNode
}
public override void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
{
}
public override void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
{
}
public override void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
{
}
private void DeclareTagHelperFields(TagHelperBlock block)
{
var declareFieldsNode = Class.Children.OfType<DeclareTagHelperFieldsIRNode>().SingleOrDefault();
if (declareFieldsNode == null)
{
declareFieldsNode = new DeclareTagHelperFieldsIRNode();
declareFieldsNode.Parent = Class;
var methodIndex = Class.Children.IndexOf(Method);
Class.Children.Insert(methodIndex, declareFieldsNode);
}
foreach (var descriptor in block.Descriptors)
{
declareFieldsNode.UsedTagHelperTypeNames.Add(descriptor.TypeName);
}
}
private void AddTagHelperCreation(IEnumerable<TagHelperDescriptor> descriptors)
{
foreach (var descriptor in descriptors)
{
var createTagHelper = new CreateTagHelperIRNode()
{
TagHelperTypeName = descriptor.TypeName,
Descriptor = descriptor
};
Builder.Add(createTagHelper);
}
}
private void AddTagHelperAttributes(IList<TagHelperAttributeNode> attributes, IEnumerable<TagHelperDescriptor> descriptors)
{
var renderedBoundAttributeNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var attribute in attributes)
{
var attributeValueNode = attribute.Value;
var associatedDescriptors = descriptors.Where(descriptor =>
descriptor.Attributes.Any(attributeDescriptor => attributeDescriptor.IsNameMatch(attribute.Name)));
if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name))
{
if (attributeValueNode == null)
{
// Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already
// logged an error if it was a bound attribute; so we can skip.
continue;
}
foreach (var associatedDescriptor in associatedDescriptors)
{
var associatedAttributeDescriptor = associatedDescriptor.Attributes.First(
attributeDescriptor => attributeDescriptor.IsNameMatch(attribute.Name));
var setTagHelperProperty = new SetTagHelperPropertyIRNode()
{
PropertyName = associatedAttributeDescriptor.PropertyName,
AttributeName = attribute.Name,
TagHelperTypeName = associatedDescriptor.TypeName,
Descriptor = associatedAttributeDescriptor,
ValueStyle = attribute.ValueStyle
};
Builder.Push(setTagHelperProperty);
attributeValueNode.Accept(this);
Builder.Pop();
}
}
else
{
var addHtmlAttribute = new AddTagHelperHtmlAttributeIRNode()
{
Name = attribute.Name,
ValueStyle = attribute.ValueStyle
};
Builder.Push(addHtmlAttribute);
if (attributeValueNode != null)
{
attributeValueNode.Accept(this);
}
Builder.Pop();
}
}
}
private void AddExecuteTagHelpers()
{
Builder.Add(new ExecuteTagHelpersIRNode());
}
private MappingLocation BuildSourceRangeFromNode(SyntaxTreeNode node)
{
var location = node.Start;

View File

@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
public RazorEngine Engine { get; set; }
public int Order => 150;
public int Order => 100;
public RazorSyntaxTree Execute(RazorCodeDocument codeDocument, RazorSyntaxTree syntaxTree)
{

View File

@ -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 System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class AddTagHelperHtmlAttributeIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public string Name { get; set; }
internal HtmlAttributeValueStyle ValueStyle { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitAddTagHelperHtmlAttribute(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitAddTagHelperHtmlAttribute(this);
}
}
}

View File

@ -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 System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class CreateTagHelperIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = EmptyArray;
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public string TagHelperTypeName { get; set; }
internal TagHelperDescriptor Descriptor { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitCreateTagHelper(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitCreateTagHelper(this);
}
}
}

View File

@ -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 System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class DeclareTagHelperFieldsIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = EmptyArray;
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public ISet<string> UsedTagHelperTypeNames { get; set; } = new HashSet<string>(StringComparer.Ordinal);
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitDeclareTagHelperFields(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitDeclareTagHelperFields(this);
}
}
}

View File

@ -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;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class ExecuteTagHelpersIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitExecuteTagHelpers(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitExecuteTagHelpers(this);
}
}
}

View File

@ -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 System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class InitializeTagHelperStructureIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public string TagName { get; set; }
internal TagMode TagMode { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitInitializeTagHelperStructure(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitInitializeTagHelperStructure(this);
}
}
}

View File

@ -93,5 +93,40 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
VisitDefault(node);
}
internal virtual void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
{
VisitDefault(node);
}
internal virtual void VisitTagHelper(TagHelperIRNode node)
{
VisitDefault(node);
}
internal virtual void VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node)
{
VisitDefault(node);
}
internal virtual void VisitCreateTagHelper(CreateTagHelperIRNode node)
{
VisitDefault(node);
}
internal virtual void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node)
{
VisitDefault(node);
}
internal virtual void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node)
{
VisitDefault(node);
}
internal virtual void VisitExecuteTagHelpers(ExecuteTagHelpersIRNode node)
{
VisitDefault(node);
}
}
}

View File

@ -94,5 +94,40 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
return VisitDefault(node);
}
internal virtual TResult VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
{
return VisitDefault(node);
}
internal virtual TResult VisitTagHelper(TagHelperIRNode node)
{
return VisitDefault(node);
}
internal virtual TResult VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node)
{
return VisitDefault(node);
}
internal virtual TResult VisitCreateTagHelper(CreateTagHelperIRNode node)
{
return VisitDefault(node);
}
internal virtual TResult VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node)
{
return VisitDefault(node);
}
internal virtual TResult VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node)
{
return VisitDefault(node);
}
internal virtual TResult VisitExecuteTagHelpers(ExecuteTagHelpersIRNode node)
{
return VisitDefault(node);
}
}
}

View File

@ -0,0 +1,48 @@
// 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 Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class SetTagHelperPropertyIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public string TagHelperTypeName { get; set; }
public string PropertyName { get; set; }
public string AttributeName { get; set; }
internal HtmlAttributeValueStyle ValueStyle { get; set; }
internal TagHelperAttributeDescriptor Descriptor { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitSetTagHelperProperty(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitSetTagHelperProperty(this);
}
}
}

View File

@ -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;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
internal class TagHelperIRNode : RazorIRNode
{
public override IList<RazorIRNode> Children { get; } = new List<RazorIRNode>();
public override RazorIRNode Parent { get; set; }
internal override MappingLocation SourceRange { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
visitor.VisitTagHelper(this);
}
public override TResult Accept<TResult>(RazorIRNodeVisitor<TResult> visitor)
{
if (visitor == null)
{
throw new ArgumentNullException(nameof(visitor));
}
return visitor.VisitTagHelper(this);
}
}
}

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public override void Accept(ParserVisitor visitor, Span span)
{
visitor.VisitAddTagHelperSpan(this, span);
}
public override void GenerateChunk(Span target, ChunkGeneratorContext context)

View File

@ -112,5 +112,25 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public virtual void VisitStartCommentBlock(RazorCommentChunkGenerator chunkGenerator, Block block)
{
}
public virtual void VisitStartTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
{
}
public virtual void VisitEndTagHelperBlock(TagHelperChunkGenerator chunkGenerator, Block block)
{
}
public virtual void VisitAddTagHelperSpan(AddTagHelperChunkGenerator chunkGenerator, Span span)
{
}
public virtual void VisitRemoveTagHelperSpan(RemoveTagHelperChunkGenerator chunkGenerator, Span span)
{
}
public virtual void VisitTagHelperPrefixDirectiveSpan(TagHelperPrefixDirectiveChunkGenerator chunkGenerator, Span span)
{
}
}
}

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public override void Accept(ParserVisitor visitor, Span span)
{
visitor.VisitRemoveTagHelperSpan(this, span);
}
public override void GenerateChunk(Span target, ChunkGeneratorContext context)

View File

@ -86,10 +86,12 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public override void AcceptStart(ParserVisitor visitor, Block block)
{
visitor.VisitStartTagHelperBlock(this, block);
}
public override void AcceptEnd(ParserVisitor visitor, Block block)
{
visitor.VisitEndTagHelperBlock(this, block);
}
}
}

View File

@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Legacy
public override void Accept(ParserVisitor visitor, Span span)
{
visitor.VisitTagHelperPrefixDirectiveSpan(this, span);
}
public override void GenerateChunk(Span target, ChunkGeneratorContext context)

View File

@ -55,8 +55,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
// Syntax Tree passes
builder.Features.Add(new DefaultDirectiveSyntaxTreePass());
builder.Features.Add(new TagHelperBinderSyntaxTreePass());
builder.Features.Add(new HtmlNodeOptimizationPass());
builder.Features.Add(new TagHelperBinderSyntaxTreePass());
// IR Passes
builder.Features.Add(new DefaultDirectiveIRPass());

View File

@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Razor.Evolution
{
public RazorEngine Engine { get; set; }
public int Order => 100;
public int Order => 150;
public RazorSyntaxTree Execute(RazorCodeDocument document, RazorSyntaxTree syntaxTree)
{

View File

@ -1,8 +1,6 @@
// 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.Evolution.Intermediate;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Razor.Evolution.Intermediate;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
{
@ -86,6 +87,31 @@ namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
WriteContentNode(node, node.Content);
}
internal override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIRNode node)
{
WriteContentNode(node, node.UsedTagHelperTypeNames.ToArray());
}
internal override void VisitInitializeTagHelperStructure(InitializeTagHelperStructureIRNode node)
{
WriteContentNode(node, node.TagName, string.Format("{0}.{1}", nameof(TagMode), node.TagMode));
}
internal override void VisitCreateTagHelper(CreateTagHelperIRNode node)
{
WriteContentNode(node, node.TagHelperTypeName);
}
internal override void VisitSetTagHelperProperty(SetTagHelperPropertyIRNode node)
{
WriteContentNode(node, node.AttributeName, node.PropertyName, string.Format("HtmlAttributeValueStyle.{0}", node.ValueStyle));
}
internal override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIRNode node)
{
WriteContentNode(node, node.Name, string.Format("{0}.{1}", nameof(HtmlAttributeValueStyle), node.ValueStyle));
}
protected void WriteBasicNode(RazorIRNode node)
{
WriteIndent();

View File

@ -0,0 +1,121 @@
// 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.Evolution.Legacy;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Evolution.IntegrationTests
{
public class TagHelpersIntegrationTest : IntegrationTestBase
{
[Fact]
public void SimpleTagHelpers()
{
// Arrange
var descriptors = new[]
{
new TagHelperDescriptor
{
TagName = "input",
TypeName = "InputTagHelper"
}
};
var engine = RazorEngine.Create(
builder => builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors))));
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
}
[Fact]
public void TagHelpersWithBoundAttributes()
{
// Arrange
var descriptors = new[]
{
new TagHelperDescriptor
{
TagName = "input",
TypeName = "InputTagHelper",
Attributes = new[] { new TagHelperAttributeDescriptor
{
Name = "bound",
PropertyName = "FooProp",
TypeName = "System.String"
} }
}
};
var engine = RazorEngine.Create(
builder => builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors))));
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
}
[Fact]
public void NestedTagHelpers()
{
// Arrange
var descriptors = new[]
{
new TagHelperDescriptor
{
TagName = "p",
TypeName = "PTagHelper"
},
new TagHelperDescriptor
{
TagName = "form",
TypeName = "FormTagHelper"
},
new TagHelperDescriptor
{
TagName = "input",
TypeName = "InputTagHelper",
Attributes = new[] { new TagHelperAttributeDescriptor
{
Name = "value",
PropertyName = "FooProp",
TypeName = "System.String"
} }
}
};
var engine = RazorEngine.Create(
builder => builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors))));
var document = CreateCodeDocument();
// Act
engine.Process(document);
// Assert
AssertIRMatchesBaseline(document.GetIRDocument());
}
private class TestTagHelperDescriptorResolver : ITagHelperDescriptorResolver
{
private readonly IEnumerable<TagHelperDescriptor> _descriptors;
public TestTagHelperDescriptorResolver(IEnumerable<TagHelperDescriptor> descriptors)
{
_descriptors = descriptors;
}
public IEnumerable<TagHelperDescriptor> Resolve(TagHelperDescriptorResolutionContext resolutionContext)
{
return _descriptors;
}
}
}
}

View File

@ -5,6 +5,9 @@ using static Microsoft.AspNetCore.Razor.Evolution.Intermediate.RazorIRAssert;
using Xunit;
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using System.Collections.Generic;
using System.Linq;
namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
{
@ -188,9 +191,106 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
n => Assert.IsType<ClassDeclarationIRNode>(n));
}
[Fact]
public void Lower_TagHelpers()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create(@"<span val=""@Hello World""></span>");
var descriptors = new[]
{
new TagHelperDescriptor
{
TagName = "span",
TypeName = "SpanTagHelper"
}
};
// Act
var irDocument = Lower(codeDocument, descriptors);
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
Children(@class,
n => TagHelperFieldDeclaration(n, "SpanTagHelper"),
n => Assert.IsType<RazorMethodDeclarationIRNode>(n));
var method = @class.Children[1];
var tagHelperNode = SingleChild<TagHelperIRNode>(method);
Children(tagHelperNode,
n => TagHelperStructure("span", TagMode.StartTagAndEndTag, n),
n => Assert.IsType<CreateTagHelperIRNode>(n),
n => TagHelperHtmlAttribute(
"val",
HtmlAttributeValueStyle.DoubleQuotes,
n,
v => CSharpAttributeValue(string.Empty, "Hello", v),
v => LiteralAttributeValue(" ", "World", v)),
n => Assert.IsType<ExecuteTagHelpersIRNode>(n));
}
[Fact]
public void Lower_TagHelpersWithBoundAttribute()
{
// Arrange
var codeDocument = TestRazorCodeDocument.Create("<input bound='foo' />");
var descriptor = new TagHelperDescriptor
{
TagName = "input",
TypeName = "InputTagHelper",
Attributes = new[] { new TagHelperAttributeDescriptor
{
Name = "bound",
PropertyName = "FooProp",
TypeName = "System.String"
} }
};
// Act
var irDocument = Lower(codeDocument, new[] { descriptor });
// Assert
Children(irDocument,
n => Assert.IsType<ChecksumIRNode>(n),
n => Assert.IsType<NamespaceDeclarationIRNode>(n));
var @namespace = irDocument.Children[1];
Children(@namespace,
n => Using("System", n),
n => Using(typeof(Task).Namespace, n),
n => Assert.IsType<ClassDeclarationIRNode>(n));
var @class = @namespace.Children[2];
Children(@class,
n => TagHelperFieldDeclaration(n, "InputTagHelper"),
n => Assert.IsType<RazorMethodDeclarationIRNode>(n));
var method = @class.Children[1];
var tagHelperNode = SingleChild<TagHelperIRNode>(method);
Children(tagHelperNode,
n => TagHelperStructure("input", TagMode.SelfClosing, n),
n => Assert.IsType<CreateTagHelperIRNode>(n),
n => SetTagHelperProperty(
"bound",
"FooProp",
HtmlAttributeValueStyle.SingleQuotes,
n,
v => Html("foo", v)),
n => Assert.IsType<ExecuteTagHelpersIRNode>(n));
}
private DocumentIRNode Lower(RazorCodeDocument codeDocument)
{
var engine = RazorEngine.Create();
return Lower(codeDocument, Enumerable.Empty<TagHelperDescriptor>());
}
private DocumentIRNode Lower(RazorCodeDocument codeDocument, IEnumerable<TagHelperDescriptor> descriptors)
{
var engine = RazorEngine.Create(
builder => builder.Features.Add(new TagHelperFeature(new TestTagHelperDescriptorResolver(descriptors))));
for (var i = 0; i < engine.Phases.Count; i++)
{
@ -207,5 +307,20 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
Assert.NotNull(irDocument);
return irDocument;
}
private class TestTagHelperDescriptorResolver : ITagHelperDescriptorResolver
{
private readonly IEnumerable<TagHelperDescriptor> _descriptors;
public TestTagHelperDescriptorResolver(IEnumerable<TagHelperDescriptor> descriptors)
{
_descriptors = descriptors;
}
public IEnumerable<TagHelperDescriptor> Resolve(TagHelperDescriptorResolutionContext resolutionContext)
{
return _descriptors;
}
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Razor.Evolution.Legacy;
using Xunit;
using Xunit.Sdk;
@ -188,6 +189,77 @@ namespace Microsoft.AspNetCore.Razor.Evolution.Intermediate
}
}
internal static void TagHelperFieldDeclaration(RazorIRNode node, params string[] tagHelperTypes)
{
var declareTagHelperFields = Assert.IsType<DeclareTagHelperFieldsIRNode>(node);
try
{
Assert.Equal(tagHelperTypes, declareTagHelperFields.UsedTagHelperTypeNames);
}
catch (XunitException e)
{
throw new IRAssertException(declareTagHelperFields, e.Message);
}
}
internal static void TagHelperStructure(string tagName, TagMode tagMode, RazorIRNode node)
{
var tagHelperStructureNode = Assert.IsType<InitializeTagHelperStructureIRNode>(node);
try
{
Assert.Equal(tagName, tagHelperStructureNode.TagName);
Assert.Equal(tagMode, tagHelperStructureNode.TagMode);
}
catch (XunitException e)
{
throw new IRAssertException(tagHelperStructureNode, e.Message);
}
}
internal static void TagHelperHtmlAttribute(
string name,
HtmlAttributeValueStyle valueStyle,
RazorIRNode node,
params Action<RazorIRNode>[] valueValidators)
{
var tagHelperHtmlAttribute = Assert.IsType<AddTagHelperHtmlAttributeIRNode>(node);
try
{
Assert.Equal(name, tagHelperHtmlAttribute.Name);
Assert.Equal(valueStyle, tagHelperHtmlAttribute.ValueStyle);
Children(tagHelperHtmlAttribute, valueValidators);
}
catch (XunitException e)
{
throw new IRAssertException(tagHelperHtmlAttribute, tagHelperHtmlAttribute.Children, e.Message, e);
}
}
internal static void SetTagHelperProperty(
string name,
string propertyName,
HtmlAttributeValueStyle valueStyle,
RazorIRNode node,
params Action<RazorIRNode>[] valueValidators)
{
var tagHelperBoundAttribute = Assert.IsType<SetTagHelperPropertyIRNode>(node);
try
{
Assert.Equal(name, tagHelperBoundAttribute.AttributeName);
Assert.Equal(propertyName, tagHelperBoundAttribute.PropertyName);
Assert.Equal(valueStyle, tagHelperBoundAttribute.ValueStyle);
Children(tagHelperBoundAttribute, valueValidators);
}
catch (XunitException e)
{
throw new IRAssertException(tagHelperBoundAttribute, tagHelperBoundAttribute.Children, e.Message, e);
}
}
private class IRAssertException : XunitException
{
public IRAssertException(RazorIRNode node, string userMessage)

View File

@ -137,8 +137,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Assert.Collection(
features,
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature));
}
@ -158,8 +158,8 @@ namespace Microsoft.AspNetCore.Razor.Evolution
Assert.Collection(
features,
feature => Assert.IsType<DefaultDirectiveSyntaxTreePass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<HtmlNodeOptimizationPass>(feature),
feature => Assert.IsType<TagHelperBinderSyntaxTreePass>(feature),
feature => Assert.IsType<DefaultDirectiveIRPass>(feature),
feature => Assert.IsType<RazorEngine.ConfigureDesignTimeOptions>(feature));
}

View File

@ -0,0 +1,5 @@
@addTagHelper *, TestAssembly
<p someattr>Hola</p>
<form unbound="foo">
<input value=Hello type='text' />
</form>

View File

@ -0,0 +1,32 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - System
UsingStatement - - System.Threading.Tasks
ClassDeclaration - - - - -
DeclareTagHelperFields - - PTagHelper - FormTagHelper - InputTagHelper
RazorMethodDeclaration - - - - -
HtmlContent - (0:0,0 [0] NestedTagHelpers.cshtml) -
TagHelper -
InitializeTagHelperStructure - - p - TagMode.StartTagAndEndTag
HtmlContent - (43:1,12 [4] NestedTagHelpers.cshtml) - Hola
CreateTagHelper - - PTagHelper
AddTagHelperHtmlAttribute - - someattr - HtmlAttributeValueStyle.Minimized
ExecuteTagHelpers -
HtmlContent - (51:1,20 [2] NestedTagHelpers.cshtml) - \n
TagHelper -
InitializeTagHelperStructure - - form - TagMode.StartTagAndEndTag
HtmlContent - (73:2,20 [6] NestedTagHelpers.cshtml) - \n
TagHelper -
InitializeTagHelperStructure - - input - TagMode.SelfClosing
CreateTagHelper - - InputTagHelper
SetTagHelperProperty - - value - FooProp - HtmlAttributeValueStyle.DoubleQuotes
HtmlContent - (92:3,17 [5] NestedTagHelpers.cshtml) - Hello
AddTagHelperHtmlAttribute - - type - HtmlAttributeValueStyle.SingleQuotes
HtmlContent - (104:3,29 [4] NestedTagHelpers.cshtml) - text
ExecuteTagHelpers -
HtmlContent - (112:3,37 [2] NestedTagHelpers.cshtml) - \n
CreateTagHelper - - FormTagHelper
AddTagHelperHtmlAttribute - - unbound - HtmlAttributeValueStyle.DoubleQuotes
HtmlContent - (68:2,15 [3] NestedTagHelpers.cshtml) - foo
ExecuteTagHelpers -

View File

@ -0,0 +1,5 @@
@addTagHelper *, TestAssembly
<p>Hola</p>
<form>
<input value='Hello' type='text' />
</form>

View File

@ -0,0 +1,18 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - System
UsingStatement - - System.Threading.Tasks
ClassDeclaration - - - - -
DeclareTagHelperFields - - InputTagHelper
RazorMethodDeclaration - - - - -
HtmlContent - (0:0,0 [0] SimpleTagHelpers.cshtml) - <p>Hola</p>\n<form>\n
TagHelper -
InitializeTagHelperStructure - - input - TagMode.SelfClosing
CreateTagHelper - - InputTagHelper
AddTagHelperHtmlAttribute - - value - HtmlAttributeValueStyle.SingleQuotes
HtmlContent - (70:3,18 [5] SimpleTagHelpers.cshtml) - Hello
AddTagHelperHtmlAttribute - - type - HtmlAttributeValueStyle.SingleQuotes
HtmlContent - (83:3,31 [4] SimpleTagHelpers.cshtml) - text
ExecuteTagHelpers -
HtmlContent - (91:3,39 [2] SimpleTagHelpers.cshtml) - \n</form>

View File

@ -0,0 +1,4 @@
@addTagHelper *, TestAssembly
<form>
<input bound=@Hello type='text' />
</form>

View File

@ -0,0 +1,19 @@
Document -
Checksum -
NamespaceDeclaration - -
UsingStatement - - System
UsingStatement - - System.Threading.Tasks
ClassDeclaration - - - - -
DeclareTagHelperFields - - InputTagHelper
RazorMethodDeclaration - - - - -
HtmlContent - (0:0,0 [0] TagHelpersWithBoundAttributes.cshtml) - <form>\n
TagHelper -
InitializeTagHelperStructure - - input - TagMode.SelfClosing
CreateTagHelper - - InputTagHelper
SetTagHelperProperty - - bound - FooProp - HtmlAttributeValueStyle.DoubleQuotes
CSharpExpression - (57:2,18 [5] TagHelpersWithBoundAttributes.cshtml)
CSharpToken - (57:2,18 [5] TagHelpersWithBoundAttributes.cshtml) - Hello
AddTagHelperHtmlAttribute - - type - HtmlAttributeValueStyle.SingleQuotes
HtmlContent - (69:2,30 [4] TagHelpersWithBoundAttributes.cshtml) - text
ExecuteTagHelpers -
HtmlContent - (77:2,38 [2] TagHelpersWithBoundAttributes.cshtml) - \n</form>