diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs index 7907afd6b8..953453710f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/InstrumentationPass.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Globalization; using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions @@ -103,17 +104,16 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions Items.Add(new InstrumentationItem(node, Parent, isLiteral: false, source: node.Source.Value)); } - VisitDefault(node); - } - - public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIntermediateNode node) - { - // We don't want to instrument TagHelper attributes. Do nothing. - } - - public override void VisitSetTagHelperProperty(SetTagHelperPropertyIntermediateNode node) - { - // We don't want to instrument TagHelper attributes. Do nothing. + // Inside a tag helper we only want to visit inside of the body (skip all of the attributes and properties). + for (var i = 0; i < node.Children.Count; i++) + { + var child = node.Children[i]; + if (child is TagHelperBodyIntermediateNode || + child is DefaultTagHelperBodyIntermediateNode) + { + VisitDefault(child); + } + } } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs index deb7cbb766..b01382353c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ModelExpressionPass.cs @@ -23,11 +23,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public List TagHelpers { get; } = new List(); - public override void VisitSetTagHelperProperty(SetTagHelperPropertyIntermediateNode node) + public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node) { - if (string.Equals(node.Descriptor.TypeName, ModelExpressionTypeName, StringComparison.Ordinal) || + if (string.Equals(node.BoundAttribute.TypeName, ModelExpressionTypeName, StringComparison.Ordinal) || (node.IsIndexerNameMatch && - string.Equals(node.Descriptor.IndexerTypeName, ModelExpressionTypeName, StringComparison.Ordinal))) + string.Equals(node.BoundAttribute.IndexerTypeName, ModelExpressionTypeName, StringComparison.Ordinal))) { var expression = new CSharpExpressionIntermediateNode(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs index aca2e80833..405b7bdc19 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTagHelperPass.cs @@ -1,106 +1,168 @@ // 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.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Extensions; using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Mvc.Razor.Extensions { public class ViewComponentTagHelperPass : IntermediateNodePassBase, IRazorOptimizationPass { + // Run after the default taghelper pass + public override int Order => IntermediateNodePassBase.DefaultFeatureOrder + 2000; + private static readonly string[] PublicModifiers = new[] { "public" }; protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { - var visitor = new Visitor(); - visitor.Visit(documentNode); - - if (visitor.Class == null || visitor.TagHelpers.Count == 0) + var @namespace = documentNode.FindPrimaryNamespace(); + var @class = documentNode.FindPrimaryClass(); + if (@namespace == null || @class == null) { - // Nothing to do, bail. + // Nothing to do, bail. We can't function without the standard structure. return; } - foreach (var tagHelper in visitor.TagHelpers) - { - GenerateVCTHClass(visitor.Class, tagHelper.Value); + var context = new Context(@namespace, @class); - var tagHelperTypeName = tagHelper.Value.GetTypeName(); - if (visitor.Fields.UsedTagHelperTypeNames.Remove(tagHelperTypeName)) + // For each VCTH *usage* we need to rewrite the tag helper node to use the tag helper runtime to construct + // and set properties on the the correct field, and using the name of the type we will generate. + var nodes = documentNode.FindDescendantNodes(); + for (var i = 0; i < nodes.Count; i++) + { + var node = nodes[i]; + foreach (var tagHelper in node.TagHelpers) { - visitor.Fields.UsedTagHelperTypeNames.Add(GetVCTHFullName(visitor.Namespace, visitor.Class, tagHelper.Value)); + RewriteUsage(context, node, tagHelper); } } - foreach (var (parent, node) in visitor.CreateTagHelpers) + // Then for each VCTH *definition* that we've seen we need to generate the class that implements + // ITagHelper and the field that will hold it. + foreach (var tagHelper in context.TagHelpers) { - RewriteCreateNode(visitor.Namespace, visitor.Class, (CreateTagHelperIntermediateNode)node, parent); + AddField(context, tagHelper); + AddTagHelperClass(context, tagHelper); } } - private void GenerateVCTHClass(ClassDeclarationIntermediateNode @class, TagHelperDescriptor tagHelper) + private void RewriteUsage(Context context, TagHelperIntermediateNode node, TagHelperDescriptor tagHelper) + { + if (!tagHelper.IsViewComponentKind()) + { + return; + } + + context.Add(tagHelper); + + // Now we need to insert a create node using the default tag helper runtime. This is similar to + // code in DefaultTagHelperOptimizationPass. + // + // Find the body node. + var i = 0; + while (i < node.Children.Count && node.Children[i] is TagHelperBodyIntermediateNode) + { + i++; + } + while (i < node.Children.Count && node.Children[i] is DefaultTagHelperBodyIntermediateNode) + { + i++; + } + + // Now find the last create node. + while (i < node.Children.Count && node.Children[i] is DefaultTagHelperCreateIntermediateNode) + { + i++; + } + + // Now i has the right insertion point. + node.Children.Insert(i, new DefaultTagHelperCreateIntermediateNode() + { + Field = context.GetFieldName(tagHelper), + TagHelper = tagHelper, + Type = context.GetFullyQualifiedName(tagHelper), + }); + + // Now we need to rewrite any set property nodes to use the default runtime. + for (i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode && + propertyNode.TagHelper == tagHelper) + { + // This is a set property for this VCTH - we need to replace it with a node + // that will use our field and property name. + node.Children[i] = new DefaultTagHelperPropertyIntermediateNode(propertyNode) + { + Field = context.GetFieldName(tagHelper), + Property = propertyNode.BoundAttribute.GetPropertyName(), + }; + } + } + } + + private void AddField(Context context, TagHelperDescriptor tagHelper) + { + // We need to insert a node for the field that will hold the tag helper. We've already generated a field name + // at this time and use it for all uses of the same tag helper type. + // + // We also want to preserve the ordering of the nodes for testability. So insert at the end of any existing + // field nodes. + var i = 0; + while (i < context.Class.Children.Count && context.Class.Children[i] is DefaultTagHelperRuntimeIntermediateNode) + { + i++; + } + + while (i < context.Class.Children.Count && context.Class.Children[i] is FieldDeclarationIntermediateNode) + { + i++; + } + + context.Class.Children.Insert(i, new FieldDeclarationIntermediateNode() + { + Annotations = + { + { CommonAnnotations.DefaultTagHelperExtension.TagHelperField, bool.TrueString }, + }, + Modifiers = + { + "private", + }, + Name = context.GetFieldName(tagHelper), + Type = "global::" + context.GetFullyQualifiedName(tagHelper), + }); + } + + private void AddTagHelperClass(Context context, TagHelperDescriptor tagHelper) { var writer = new CodeWriter(); - WriteClass(writer, tagHelper); + WriteClass(context, writer, tagHelper); - var statement = new CSharpCodeIntermediateNode(); - statement.Children.Add(new IntermediateToken() + var code = new CSharpCodeIntermediateNode(); + code.Children.Add(new IntermediateToken() { Kind = IntermediateToken.TokenKind.CSharp, Content = writer.GenerateCode() }); - @class.Children.Add(statement); + context.Class.Children.Add(code); } - private void RewriteCreateNode( - NamespaceDeclarationIntermediateNode @namespace, - ClassDeclarationIntermediateNode @class, - CreateTagHelperIntermediateNode node, - IntermediateNode parent) - { - var newTypeName = GetVCTHFullName(@namespace, @class, node.Descriptor); - for (var i = 0; i < parent.Children.Count; i++) - { - if (parent.Children[i] is SetTagHelperPropertyIntermediateNode setProperty && - node.Descriptor.BoundAttributes.Contains(setProperty.Descriptor)) - { - setProperty.TagHelperTypeName = newTypeName; - } - } - - node.TagHelperTypeName = newTypeName; - } - - private static string GetVCTHFullName( - NamespaceDeclarationIntermediateNode @namespace, - ClassDeclarationIntermediateNode @class, - TagHelperDescriptor tagHelper) - { - var vcName = tagHelper.GetViewComponentName(); - return $"{@namespace.Content}.{@class.Name}.__Generated__{vcName}ViewComponentTagHelper"; - } - - private static string GetVCTHClassName( - TagHelperDescriptor tagHelper) - { - var vcName = tagHelper.GetViewComponentName(); - return $"__Generated__{vcName}ViewComponentTagHelper"; - } - - private void WriteClass(CodeWriter writer, TagHelperDescriptor descriptor) + private void WriteClass(Context context, CodeWriter writer, TagHelperDescriptor tagHelper) { // Add target element. - BuildTargetElementString(writer, descriptor); + BuildTargetElementString(writer, tagHelper); // Initialize declaration. var tagHelperTypeName = "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper"; - var className = GetVCTHClassName(descriptor); + var className = context.GetClassName(tagHelper); using (writer.BuildClassDeclaration(PublicModifiers, className, tagHelperTypeName, interfaces: null)) { @@ -114,10 +176,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions BuildConstructorString(writer, className); // Add attributes. - BuildAttributeDeclarations(writer, descriptor); + BuildAttributeDeclarations(writer, tagHelper); // Add process method. - BuildProcessMethodString(writer, descriptor); + BuildProcessMethodString(writer, tagHelper); } } @@ -136,7 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } } - private void BuildAttributeDeclarations(CodeWriter writer, TagHelperDescriptor descriptor) + private void BuildAttributeDeclarations(CodeWriter writer, TagHelperDescriptor tagHelper) { writer.Write("[") .Write("Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeNotBoundAttribute") @@ -149,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions $"global::Microsoft.AspNetCore.Mvc.Rendering.ViewContext", "ViewContext"); - foreach (var attribute in descriptor.BoundAttributes) + foreach (var attribute in tagHelper.BoundAttributes) { writer.WriteAutoPropertyDeclaration( PublicModifiers, @@ -165,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } } - private void BuildProcessMethodString(CodeWriter writer, TagHelperDescriptor descriptor) + private void BuildProcessMethodString(CodeWriter writer, TagHelperDescriptor tagHelper) { var contextVariable = "context"; var outputVariable = "output"; @@ -185,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions "Contextualize", new[] { "ViewContext" }); - var methodParameters = GetMethodParameters(descriptor); + var methodParameters = GetMethodParameters(tagHelper); var contentVariable = "content"; writer.Write("var ") .WriteStartAssignment(contentVariable) @@ -199,22 +261,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions } } - private string[] GetMethodParameters(TagHelperDescriptor descriptor) + private string[] GetMethodParameters(TagHelperDescriptor tagHelper) { - var propertyNames = descriptor.BoundAttributes.Select(attribute => attribute.GetPropertyName()); + var propertyNames = tagHelper.BoundAttributes.Select(attribute => attribute.GetPropertyName()); var joinedPropertyNames = string.Join(", ", propertyNames); var parametersString = $"new {{ { joinedPropertyNames } }}"; - - var viewComponentName = descriptor.GetViewComponentName(); + var viewComponentName = tagHelper.GetViewComponentName(); var methodParameters = new[] { $"\"{viewComponentName}\"", parametersString }; return methodParameters; } - private void BuildTargetElementString(CodeWriter writer, TagHelperDescriptor descriptor) + private void BuildTargetElementString(CodeWriter writer, TagHelperDescriptor tagHelper) { - Debug.Assert(descriptor.TagMatchingRules.Count() == 1); + Debug.Assert(tagHelper.TagMatchingRules.Count() == 1); - var rule = descriptor.TagMatchingRules.First(); + var rule = tagHelper.TagMatchingRules.First(); writer.Write("[") .WriteStartMethodInvocation("Microsoft.AspNetCore.Razor.TagHelpers.HtmlTargetElementAttribute") @@ -222,59 +283,59 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions .WriteLine(")]"); } - private class Visitor : IntermediateNodeWalker + private struct Context { - public ClassDeclarationIntermediateNode Class { get; private set; } + private Dictionary _tagHelpers; - public DeclareTagHelperFieldsIntermediateNode Fields { get; private set; } - - public NamespaceDeclarationIntermediateNode Namespace { get; private set; } - - public List CreateTagHelpers { get; } = new List(); - - public Dictionary TagHelpers { get; } = new Dictionary(); - - public override void VisitCreateTagHelper(CreateTagHelperIntermediateNode node) + public Context(NamespaceDeclarationIntermediateNode @namespace, ClassDeclarationIntermediateNode @class) { - var tagHelper = node.Descriptor; - if (tagHelper.IsViewComponentKind()) - { - // Capture all the VCTagHelpers (unique by type name) so we can generate a class for each one. - var vcName = tagHelper.GetViewComponentName(); - TagHelpers[vcName] = tagHelper; + Namespace = @namespace; + Class = @class; - CreateTagHelpers.Add(new IntermediateNodeReference(Parent, node)); - } + _tagHelpers = new Dictionary(); } - public override void VisitNamespaceDeclaration(NamespaceDeclarationIntermediateNode node) + public ClassDeclarationIntermediateNode Class { get; } + + public NamespaceDeclarationIntermediateNode Namespace { get; } + + + public IEnumerable TagHelpers => _tagHelpers.Keys; + + public bool Add(TagHelperDescriptor tagHelper) { - if (Namespace == null) + if (_tagHelpers.ContainsKey(tagHelper)) { - Namespace = node; + return false; } - base.VisitNamespaceDeclaration(node); + var className = $"__Generated__{tagHelper.GetViewComponentName()}ViewComponentTagHelper"; + var fullyQualifiedName = $"{Namespace.Content}.{Class.Name}.{className}"; + var fieldName = GenerateFieldName(tagHelper); + + _tagHelpers.Add(tagHelper, (className, fullyQualifiedName, fieldName)); + + return true; } - public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) + public string GetClassName(TagHelperDescriptor taghelper) { - if (Class == null) - { - Class = node; - } - - base.VisitClassDeclaration(node); + return _tagHelpers[taghelper].className; } - public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIntermediateNode node) + public string GetFullyQualifiedName(TagHelperDescriptor taghelper) { - if (Fields == null) - { - Fields = node; - } + return _tagHelpers[taghelper].fullyQualifiedName; + } - base.VisitDeclareTagHelperFields(node); + public string GetFieldName(TagHelperDescriptor taghelper) + { + return _tagHelpers[taghelper].fieldName; + } + + private static string GenerateFieldName(TagHelperDescriptor tagHelper) + { + return $"__{tagHelper.GetViewComponentName()}ViewComponentTagHelper"; } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs index e1bab599f2..616eb3d52e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/BoundAttributeDescriptorExtensions.cs @@ -7,16 +7,25 @@ namespace Microsoft.AspNetCore.Razor.Language { public static class BoundAttributeDescriptorExtensions { - public static string GetPropertyName(this BoundAttributeDescriptor descriptor) + public static string GetPropertyName(this BoundAttributeDescriptor attribute) { - descriptor.Metadata.TryGetValue(TagHelperMetadata.Common.PropertyName, out var propertyName); + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + attribute.Metadata.TryGetValue(TagHelperMetadata.Common.PropertyName, out var propertyName); return propertyName; } - public static bool IsDefaultKind(this BoundAttributeDescriptor descriptor) + public static bool IsDefaultKind(this BoundAttributeDescriptor attribute) { - return string.Equals(descriptor.Kind, TagHelperConventions.DefaultKind, StringComparison.Ordinal); + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + return string.Equals(attribute.Kind, TagHelperConventions.DefaultKind, StringComparison.Ordinal); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriter.cs index 9dde08cecb..8f78f1f92e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/CodeWriter.cs @@ -582,15 +582,15 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration return new CSharpCodeWritingScope(this); } - public IDisposable BuildLinePragma(SourceSpan documentLocation) + public IDisposable BuildLinePragma(SourceSpan? span) { - if (string.IsNullOrEmpty(documentLocation.FilePath)) + if (string.IsNullOrEmpty(span?.FilePath)) { // Can't build a valid line pragma without a file path. return NullDisposable.Default; } - return new LinePragmaWriter(this, documentLocation); + return new LinePragmaWriter(this, span.Value); } private void WriteVerbatimStringLiteral(string literal) diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs index 10a6c97d42..45439ccfa6 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DefaultDocumentWriter.cs @@ -203,11 +203,6 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration Context.NodeWriter.WriteHtmlContent(Context, node); } - public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIntermediateNode node) - { - Context.TagHelperWriter.WriteDeclareTagHelperFields(Context, node); - } - public override void VisitTagHelper(TagHelperIntermediateNode node) { var tagHelperRenderingContext = new TagHelperRenderingContext() @@ -218,30 +213,10 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration using (Context.Push(tagHelperRenderingContext)) { - Context.TagHelperWriter.WriteTagHelper(Context, node); + RenderChildren(node); } } - public override void VisitTagHelperBody(TagHelperBodyIntermediateNode node) - { - Context.TagHelperWriter.WriteTagHelperBody(Context, node); - } - - public override void VisitCreateTagHelper(CreateTagHelperIntermediateNode node) - { - Context.TagHelperWriter.WriteCreateTagHelper(Context, node); - } - - public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIntermediateNode node) - { - Context.TagHelperWriter.WriteAddTagHelperHtmlAttribute(Context, node); - } - - public override void VisitSetTagHelperProperty(SetTagHelperPropertyIntermediateNode node) - { - Context.TagHelperWriter.WriteSetTagHelperProperty(Context, node); - } - public override void VisitDefault(IntermediateNode node) { Context.RenderChildren(node); diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs index 9e63d30501..2668fb9eeb 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/DesignTimeTagHelperWriter.cs @@ -12,209 +12,5 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { internal class DesignTimeTagHelperWriter : TagHelperWriter { - public string CreateTagHelperMethodName { get; set; } = "CreateTagHelper"; - - public override void WriteDeclareTagHelperFields(CodeRenderingContext context, DeclareTagHelperFieldsIntermediateNode node) - { - foreach (var tagHelperTypeName in node.UsedTagHelperTypeNames) - { - var tagHelperVariableName = GetTagHelperVariableName(tagHelperTypeName); - context.CodeWriter - .Write("private global::") - .WriteVariableDeclaration( - tagHelperTypeName, - tagHelperVariableName, - value: null); - } - } - - public override void WriteTagHelper(CodeRenderingContext context, TagHelperIntermediateNode node) - { - context.RenderChildren(node); - } - - public override void WriteTagHelperBody(CodeRenderingContext context, TagHelperBodyIntermediateNode node) - { - context.RenderChildren(node); - } - - public override void WriteCreateTagHelper(CodeRenderingContext context, CreateTagHelperIntermediateNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - - context.CodeWriter - .WriteStartAssignment(tagHelperVariableName) - .Write(CreateTagHelperMethodName) - .WriteLine($"();"); - } - - public override void WriteAddTagHelperHtmlAttribute(CodeRenderingContext context, AddTagHelperHtmlAttributeIntermediateNode node) - { - context.RenderChildren(node); - } - - public override void WriteSetTagHelperProperty(CodeRenderingContext context, SetTagHelperPropertyIntermediateNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var tagHelperRenderingContext = context.TagHelperRenderingContext; - var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); - - if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out string previousValueAccessor)) - { - context.CodeWriter - .WriteStartAssignment(propertyValueAccessor) - .Write(previousValueAccessor) - .WriteLine(";"); - - return; - } - else - { - tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; - } - - if (node.Descriptor.IsStringProperty || (node.IsIndexerNameMatch && node.Descriptor.IsIndexerStringProperty)) - { - context.RenderChildren(node); - - context.CodeWriter.WriteStartAssignment(propertyValueAccessor); - if (node.Children.Count == 1 && node.Children.First() is HtmlContentIntermediateNode htmlNode) - { - var content = GetContent(htmlNode); - context.CodeWriter.WriteStringLiteral(content); - } - else - { - context.CodeWriter.Write("string.Empty"); - } - context.CodeWriter.WriteLine(";"); - } - else - { - var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode; - var valueStart = firstMappedChild?.Source; - - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) - { - var assignmentPrefixLength = propertyValueAccessor.Length + " = ".Length; - if (node.Descriptor.IsEnum && - node.Children.Count == 1 && - node.Children.First() is IntermediateToken token && - token.IsCSharp) - { - assignmentPrefixLength += $"global::{node.Descriptor.TypeName}.".Length; - - if (valueStart != null) - { - context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source.Value, context); - } - - context.CodeWriter - .WriteStartAssignment(propertyValueAccessor) - .Write("global::") - .Write(node.Descriptor.TypeName) - .Write("."); - } - else - { - if (valueStart != null) - { - context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source.Value, context); - } - - context.CodeWriter.WriteStartAssignment(propertyValueAccessor); - } - - RenderTagHelperAttributeInline(context, node, node.Source.Value); - - context.CodeWriter.WriteLine(";"); - } - } - } - - private void RenderTagHelperAttributeInline( - CodeRenderingContext context, - SetTagHelperPropertyIntermediateNode property, - SourceSpan documentLocation) - { - for (var i = 0; i < property.Children.Count; i++) - { - RenderTagHelperAttributeInline(context, property, property.Children[i], documentLocation); - } - } - - private void RenderTagHelperAttributeInline( - CodeRenderingContext context, - SetTagHelperPropertyIntermediateNode property, - IntermediateNode node, - SourceSpan documentLocation) - { - if (node is CSharpExpressionIntermediateNode || node is HtmlContentIntermediateNode) - { - for (var i = 0; i < node.Children.Count; i++) - { - RenderTagHelperAttributeInline(context, property, node.Children[i], documentLocation); - } - } - else if (node is IntermediateToken token) - { - if (node.Source != null) - { - context.AddLineMappingFor(node); - } - - context.CodeWriter.Write(token.Content); - } - else if (node is CSharpCodeIntermediateNode) - { - var error = new RazorError( - LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - documentLocation.Length); - context.Diagnostics.Add(RazorDiagnostic.Create(error)); - } - else if (node is TemplateIntermediateNode) - { - var expectedTypeName = property.IsIndexerNameMatch ? property.Descriptor.IndexerTypeName : property.Descriptor.TypeName; - var error = new RazorError( - LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(expectedTypeName), - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - documentLocation.Length); - context.Diagnostics.Add(RazorDiagnostic.Create(error)); - } - } - - private string GetContent(HtmlContentIntermediateNode node) - { - var builder = new StringBuilder(); - for (var i = 0; i < node.Children.Count; i++) - { - if (node.Children[i] is IntermediateToken token && token.IsHtml) - { - builder.Append(token.Content); - } - } - - return builder.ToString(); - } - - private static string GetTagHelperVariableName(string tagHelperTypeName) => "__" + tagHelperTypeName.Replace('.', '_'); - - private static string GetTagHelperPropertyAccessor( - bool isIndexerNameMatch, - string tagHelperVariableName, - string attributeName, - BoundAttributeDescriptor descriptor) - { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; - - if (isIndexerNameMatch) - { - var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length); - propertyAccessor += $"[\"{dictionaryKey}\"]"; - } - - return propertyAccessor; - } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeNodeWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeNodeWriter.cs index 289561229f..9f08216548 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeNodeWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeNodeWriter.cs @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration var suffixLocation = node.Source.Value.AbsoluteIndex + node.Source.Value.Length - node.Suffix.Length; context.CodeWriter .WriteStartMethodInvocation(BeginWriteAttributeMethod) - .WriteStringLiteral(node.Name) + .WriteStringLiteral(node.AttributeName) .WriteParameterSeparator() .WriteStringLiteral(node.Prefix) .WriteParameterSeparator() diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs index 820d702ae8..1155a3c19f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/RuntimeTagHelperWriter.cs @@ -12,484 +12,5 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { internal class RuntimeTagHelperWriter : TagHelperWriter { - public virtual string WriteTagHelperOutputMethod { get; set; } = "Write"; - - public string StringValueBufferVariableName { get; set; } = "__tagHelperStringValueBuffer"; - - public string ExecutionContextTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext"; - - public string ExecutionContextVariableName { get; set; } = "__tagHelperExecutionContext"; - - public string ExecutionContextAddMethodName { get; set; } = "Add"; - - public string ExecutionContextOutputPropertyName { get; set; } = "Output"; - - public string ExecutionContextSetOutputContentAsyncMethodName { get; set; } = "SetOutputContentAsync"; - - public string ExecutionContextAddHtmlAttributeMethodName { get; set; } = "AddHtmlAttribute"; - - public string ExecutionContextAddTagHelperAttributeMethodName { get; set; } = "AddTagHelperAttribute"; - - public string RunnerTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner"; - - public string RunnerVariableName { get; set; } = "__tagHelperRunner"; - - public string RunnerRunAsyncMethodName { get; set; } = "RunAsync"; - - public string ScopeManagerTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager"; - - public string ScopeManagerVariableName { get; set; } = "__tagHelperScopeManager"; - - public string ScopeManagerBeginMethodName { get; set; } = "Begin"; - - public string ScopeManagerEndMethodName { get; set; } = "End"; - - public string StartTagHelperWritingScopeMethodName { get; set; } = "StartTagHelperWritingScope"; - - public string EndTagHelperWritingScopeMethodName { get; set; } = "EndTagHelperWritingScope"; - - public string TagModeTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode"; - - public string HtmlAttributeValueStyleTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle"; - - public string CreateTagHelperMethodName { get; set; } = "CreateTagHelper"; - - public string TagHelperOutputIsContentModifiedPropertyName { get; set; } = "IsContentModified"; - - public string BeginAddHtmlAttributeValuesMethodName { get; set; } = "BeginAddHtmlAttributeValues"; - - public string EndAddHtmlAttributeValuesMethodName { get; set; } = "EndAddHtmlAttributeValues"; - - public string BeginWriteTagHelperAttributeMethodName { get; set; } = "BeginWriteTagHelperAttribute"; - - public string EndWriteTagHelperAttributeMethodName { get; set; } = "EndWriteTagHelperAttribute"; - - public string MarkAsHtmlEncodedMethodName { get; set; } = "Html.Raw"; - - public string FormatInvalidIndexerAssignmentMethodName { get; set; } = "InvalidTagHelperIndexerAssignment"; - - public override void WriteDeclareTagHelperFields(CodeRenderingContext context, DeclareTagHelperFieldsIntermediateNode node) - { - context.CodeWriter.WriteLine("#line hidden"); - - // Need to disable the warning "X is assigned to but never used." for the value buffer since - // whether it's used depends on how a TagHelper is used. - context.CodeWriter - .WriteLine("#pragma warning disable 0414") - .Write("private ") - .WriteVariableDeclaration("string", StringValueBufferVariableName, value: null) - .WriteLine("#pragma warning restore 0414"); - - context.CodeWriter - .Write("private ") - .WriteVariableDeclaration( - ExecutionContextTypeName, - ExecutionContextVariableName, - value: null); - - context.CodeWriter - .Write("private ") - .Write(RunnerTypeName) - .Write(" ") - .Write(RunnerVariableName) - .Write(" = new ") - .Write(RunnerTypeName) - .WriteLine("();"); - - var backedScopeManageVariableName = "__backed" + ScopeManagerVariableName; - context.CodeWriter - .Write("private ") - .WriteVariableDeclaration( - ScopeManagerTypeName, - backedScopeManageVariableName, - value: null); - - context.CodeWriter - .Write("private ") - .Write(ScopeManagerTypeName) - .Write(" ") - .WriteLine(ScopeManagerVariableName); - - using (context.CodeWriter.BuildScope()) - { - context.CodeWriter.WriteLine("get"); - using (context.CodeWriter.BuildScope()) - { - context.CodeWriter - .Write("if (") - .Write(backedScopeManageVariableName) - .WriteLine(" == null)"); - - using (context.CodeWriter.BuildScope()) - { - context.CodeWriter - .WriteStartAssignment(backedScopeManageVariableName) - .WriteStartNewObject(ScopeManagerTypeName) - .Write(StartTagHelperWritingScopeMethodName) - .WriteParameterSeparator() - .Write(EndTagHelperWritingScopeMethodName) - .WriteEndMethodInvocation(); - } - - context.CodeWriter - .Write("return ") - .Write(backedScopeManageVariableName) - .WriteLine(";"); - } - } - - foreach (var tagHelperTypeName in node.UsedTagHelperTypeNames) - { - var tagHelperVariableName = GetTagHelperVariableName(tagHelperTypeName); - context.CodeWriter - .Write("private global::") - .WriteVariableDeclaration( - tagHelperTypeName, - tagHelperVariableName, - value: null); - } - } - - public override void WriteTagHelper(CodeRenderingContext context, TagHelperIntermediateNode node) - { - context.RenderChildren(node); - - // Execute tag helpers - context.CodeWriter - .Write("await ") - .WriteStartInstanceMethodInvocation( - RunnerVariableName, - RunnerRunAsyncMethodName) - .Write(ExecutionContextVariableName) - .WriteEndMethodInvocation(); - - var tagHelperOutputAccessor = $"{ExecutionContextVariableName}.{ExecutionContextOutputPropertyName}"; - - context.CodeWriter - .Write("if (!") - .Write(tagHelperOutputAccessor) - .Write(".") - .Write(TagHelperOutputIsContentModifiedPropertyName) - .WriteLine(")"); - - using (context.CodeWriter.BuildScope()) - { - context.CodeWriter - .Write("await ") - .WriteInstanceMethodInvocation( - ExecutionContextVariableName, - ExecutionContextSetOutputContentAsyncMethodName); - } - - context.CodeWriter - .WriteStartMethodInvocation(WriteTagHelperOutputMethod) - .Write(tagHelperOutputAccessor) - .WriteEndMethodInvocation() - .WriteStartAssignment(ExecutionContextVariableName) - .WriteInstanceMethodInvocation( - ScopeManagerVariableName, - ScopeManagerEndMethodName); - } - - public override void WriteTagHelperBody(CodeRenderingContext context, TagHelperBodyIntermediateNode node) - { - // Call into the tag helper scope manager to start a new tag helper scope. - // Also capture the value as the current execution context. - context.CodeWriter - .WriteStartAssignment(ExecutionContextVariableName) - .WriteStartInstanceMethodInvocation( - ScopeManagerVariableName, - ScopeManagerBeginMethodName); - - var uniqueId = context.Items[CodeRenderingContext.SuppressUniqueIds]?.ToString(); - if (uniqueId == null) - { - uniqueId = Guid.NewGuid().ToString("N"); - } - - // Assign a unique ID for this instance of the source HTML tag. This must be unique - // per call site, e.g. if the tag is on the view twice, there should be two IDs. - context.CodeWriter.WriteStringLiteral(context.TagHelperRenderingContext.TagName) - .WriteParameterSeparator() - .Write(TagModeTypeName) - .Write(".") - .Write(context.TagHelperRenderingContext.TagMode.ToString()) - .WriteParameterSeparator() - .WriteStringLiteral(uniqueId) - .WriteParameterSeparator(); - - // We remove and redirect writers so TagHelper authors can retrieve content. - using (context.Push(new RuntimeNodeWriter())) - using (context.Push(new RuntimeTagHelperWriter())) - { - using (context.CodeWriter.BuildAsyncLambda()) - { - context.RenderChildren(node); - } - } - - context.CodeWriter.WriteEndMethodInvocation(); - } - - public override void WriteCreateTagHelper(CodeRenderingContext context, CreateTagHelperIntermediateNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - - context.CodeWriter - .WriteStartAssignment(tagHelperVariableName) - .Write(CreateTagHelperMethodName) - .WriteLine($"();"); - - context.CodeWriter.WriteInstanceMethodInvocation( - ExecutionContextVariableName, - ExecutionContextAddMethodName, - tagHelperVariableName); - } - - public override void WriteAddTagHelperHtmlAttribute(CodeRenderingContext context, AddTagHelperHtmlAttributeIntermediateNode node) - { - var attributeValueStyleParameter = $"{HtmlAttributeValueStyleTypeName}.{node.AttributeStructure}"; - var isConditionalAttributeValue = node.Children.Any( - child => child is CSharpExpressionAttributeValueIntermediateNode || child is CSharpCodeAttributeValueIntermediateNode); - - // All simple text and minimized attributes will be pre-allocated. - if (isConditionalAttributeValue) - { - // Dynamic attribute value should be run through the conditional attribute removal system. It's - // unbound and contains C#. - - // TagHelper attribute rendering is buffered by default. We do not want to write to the current - // writer. - var valuePieceCount = node.Children.Count( - child => - child is HtmlAttributeValueIntermediateNode || - child is CSharpExpressionAttributeValueIntermediateNode || - child is CSharpCodeAttributeValueIntermediateNode || - child is ExtensionIntermediateNode); - - context.CodeWriter - .WriteStartMethodInvocation(BeginAddHtmlAttributeValuesMethodName) - .Write(ExecutionContextVariableName) - .WriteParameterSeparator() - .WriteStringLiteral(node.Name) - .WriteParameterSeparator() - .Write(valuePieceCount.ToString(CultureInfo.InvariantCulture)) - .WriteParameterSeparator() - .Write(attributeValueStyleParameter) - .WriteEndMethodInvocation(); - - using (context.Push(new TagHelperHtmlAttributeRuntimeNodeWriter())) - { - context.RenderChildren(node); - } - - context.CodeWriter - .WriteMethodInvocation( - EndAddHtmlAttributeValuesMethodName, - ExecutionContextVariableName); - } - else - { - // This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or - // other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to - // determine its final value. - - // Attribute value is not plain text, must be buffered to determine its final value. - context.CodeWriter.WriteMethodInvocation(BeginWriteTagHelperAttributeMethodName); - - // We're building a writing scope around the provided chunks which captures everything written from the - // page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to - // ensure we capture all content that's written, directly or indirectly. - using (context.Push(new RuntimeNodeWriter())) - using (context.Push(new RuntimeTagHelperWriter())) - { - context.RenderChildren(node); - } - - context.CodeWriter - .WriteStartAssignment(StringValueBufferVariableName) - .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName) - .WriteStartInstanceMethodInvocation( - ExecutionContextVariableName, - ExecutionContextAddHtmlAttributeMethodName) - .WriteStringLiteral(node.Name) - .WriteParameterSeparator() - .WriteStartMethodInvocation(MarkAsHtmlEncodedMethodName) - .Write(StringValueBufferVariableName) - .WriteEndMethodInvocation(endLine: false) - .WriteParameterSeparator() - .Write(attributeValueStyleParameter) - .WriteEndMethodInvocation(); - } - } - - public override void WriteSetTagHelperProperty(CodeRenderingContext context, SetTagHelperPropertyIntermediateNode node) - { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var tagHelperRenderingContext = context.TagHelperRenderingContext; - var propertyName = node.Descriptor.GetPropertyName(); - - // Ensure that the property we're trying to set has initialized its dictionary bound properties. - if (node.IsIndexerNameMatch && - tagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelperTypeName}.{propertyName}")) - { - // Throw a reasonable Exception at runtime if the dictionary property is null. - context.CodeWriter - .Write("if (") - .Write(tagHelperVariableName) - .Write(".") - .Write(propertyName) - .WriteLine(" == null)"); - using (context.CodeWriter.BuildScope()) - { - // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName - // of InvalidOperationException type. - context.CodeWriter - .Write("throw ") - .WriteStartNewObject(nameof(InvalidOperationException)) - .WriteStartMethodInvocation(FormatInvalidIndexerAssignmentMethodName) - .WriteStringLiteral(node.AttributeName) - .WriteParameterSeparator() - .WriteStringLiteral(node.TagHelperTypeName) - .WriteParameterSeparator() - .WriteStringLiteral(propertyName) - .WriteEndMethodInvocation(endLine: false) // End of method call - .WriteEndMethodInvocation(); // End of new expression / throw statement - } - } - - var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); - - if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out var previousValueAccessor)) - { - context.CodeWriter - .WriteStartAssignment(propertyValueAccessor) - .Write(previousValueAccessor) - .WriteLine(";"); - - return; - } - else - { - tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; - } - - if (node.Descriptor.IsStringProperty || (node.IsIndexerNameMatch && node.Descriptor.IsIndexerStringProperty)) - { - context.CodeWriter.WriteMethodInvocation(BeginWriteTagHelperAttributeMethodName); - - using (context.Push(new LiteralRuntimeNodeWriter())) - { - context.RenderChildren(node); - } - - context.CodeWriter - .WriteStartAssignment(StringValueBufferVariableName) - .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName) - .WriteStartAssignment(propertyValueAccessor) - .Write(StringValueBufferVariableName) - .WriteLine(";"); - } - else - { - using (context.CodeWriter.BuildLinePragma(node.Source.Value)) - { - context.CodeWriter.WriteStartAssignment(propertyValueAccessor); - - if (node.Descriptor.IsEnum && - node.Children.Count == 1 && - node.Children.First() is IntermediateToken token && - token.IsCSharp) - { - context.CodeWriter - .Write("global::") - .Write(node.Descriptor.TypeName) - .Write("."); - } - - RenderTagHelperAttributeInline(context, node, node.Source.Value); - - context.CodeWriter.WriteLine(";"); - } - } - - // We need to inform the context of the attribute value. - context.CodeWriter - .WriteStartInstanceMethodInvocation( - ExecutionContextVariableName, - ExecutionContextAddTagHelperAttributeMethodName) - .WriteStringLiteral(node.AttributeName) - .WriteParameterSeparator() - .Write(propertyValueAccessor) - .WriteParameterSeparator() - .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}") - .WriteEndMethodInvocation(); - } - - private void RenderTagHelperAttributeInline( - CodeRenderingContext context, - SetTagHelperPropertyIntermediateNode property, - SourceSpan documentLocation) - { - for (var i = 0; i < property.Children.Count; i++) - { - RenderTagHelperAttributeInline(context, property, property.Children[i], documentLocation); - } - } - - private void RenderTagHelperAttributeInline( - CodeRenderingContext context, - SetTagHelperPropertyIntermediateNode property, - IntermediateNode node, - SourceSpan documentLocation) - { - if (node is CSharpExpressionIntermediateNode || node is HtmlContentIntermediateNode) - { - for (var i = 0; i < node.Children.Count; i++) - { - RenderTagHelperAttributeInline(context, property, node.Children[i], documentLocation); - } - } - else if (node is IntermediateToken token) - { - context.CodeWriter.Write(token.Content); - } - else if (node is CSharpCodeIntermediateNode) - { - var error = new RazorError( - LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - documentLocation.Length); - context.Diagnostics.Add(RazorDiagnostic.Create(error)); - } - else if (node is TemplateIntermediateNode) - { - var expectedTypeName = property.IsIndexerNameMatch ? property.Descriptor.IndexerTypeName : property.Descriptor.TypeName; - var error = new RazorError( - LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(expectedTypeName), - new SourceLocation(documentLocation.AbsoluteIndex, documentLocation.CharacterIndex, documentLocation.Length), - documentLocation.Length); - context.Diagnostics.Add(RazorDiagnostic.Create(error)); - } - } - - protected static string GetTagHelperPropertyAccessor( - bool isIndexerNameMatch, - string tagHelperVariableName, - string attributeName, - BoundAttributeDescriptor descriptor) - { - var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; - - if (isIndexerNameMatch) - { - var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length); - propertyAccessor += $"[\"{dictionaryKey}\"]"; - } - - return propertyAccessor; - } - - private static string GetTagHelperVariableName(string tagHelperTypeName) => "__" + tagHelperTypeName.Replace('.', '_'); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/TagHelperWriter.cs b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/TagHelperWriter.cs index 47ca6393db..8b8ba63fd1 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/TagHelperWriter.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/CodeGeneration/TagHelperWriter.cs @@ -7,16 +7,5 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration { internal abstract class TagHelperWriter { - public abstract void WriteDeclareTagHelperFields(CodeRenderingContext context, DeclareTagHelperFieldsIntermediateNode node); - - public abstract void WriteTagHelper(CodeRenderingContext context, TagHelperIntermediateNode node); - - public abstract void WriteTagHelperBody(CodeRenderingContext context, TagHelperBodyIntermediateNode node); - - public abstract void WriteCreateTagHelper(CodeRenderingContext context, CreateTagHelperIntermediateNode node); - - public abstract void WriteAddTagHelperHtmlAttribute(CodeRenderingContext context, AddTagHelperHtmlAttributeIntermediateNode node); - - public abstract void WriteSetTagHelperProperty(CodeRenderingContext context, SetTagHelperPropertyIntermediateNode node); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultItemCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultItemCollection.cs index 62457ea37e..4ae88bf8b6 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultItemCollection.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultItemCollection.cs @@ -44,6 +44,16 @@ namespace Microsoft.AspNetCore.Razor.Language public override bool IsReadOnly => false; + public override void Add(object key, object value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + _items.Add(key, value); + } + public override void Add(KeyValuePair item) { if (item.Key == null) diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs index 48394b103c..3915c5df3c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -362,7 +362,6 @@ namespace Microsoft.AspNetCore.Razor.Language private class MainSourceVisitor : LoweringVisitor { - private DeclareTagHelperFieldsIntermediateNode _tagHelperFields; private readonly string _tagHelperPrefix; public MainSourceVisitor(DocumentIntermediateNode document, IntermediateNodeBuilder builder, Dictionary namespaces, string tagHelperPrefix) @@ -380,7 +379,7 @@ namespace Microsoft.AspNetCore.Razor.Language { _builder.Push(new HtmlAttributeIntermediateNode() { - Name = chunkGenerator.Name, + AttributeName = chunkGenerator.Name, Prefix = chunkGenerator.Prefix, Suffix = chunkGenerator.Suffix, Source = BuildSourceSpanFromNode(block), @@ -619,8 +618,6 @@ namespace Microsoft.AspNetCore.Razor.Language return; } - DeclareTagHelperFields(tagHelperBlock); - var tagName = tagHelperBlock.TagName; if (_tagHelperPrefix != null) { @@ -647,7 +644,6 @@ namespace Microsoft.AspNetCore.Razor.Language _builder.Pop(); // Pop InitializeTagHelperStructureIntermediateNode - AddTagHelperCreation(tagHelperBlock.Binding); AddTagHelperAttributes(tagHelperBlock.Attributes, tagHelperBlock.Binding); _builder.Pop(); // Pop TagHelperIntermediateNode @@ -675,37 +671,6 @@ namespace Microsoft.AspNetCore.Razor.Language } } - private void DeclareTagHelperFields(TagHelperBlock block) - { - if (_tagHelperFields == null) - { - _tagHelperFields = new DeclareTagHelperFieldsIntermediateNode(); - _document.Children.Add(_tagHelperFields); - } - - foreach (var descriptor in block.Binding.Descriptors) - { - var typeName = descriptor.GetTypeName(); - _tagHelperFields.UsedTagHelperTypeNames.Add(typeName); - } - } - - private void AddTagHelperCreation(TagHelperBinding tagHelperBinding) - { - var descriptors = tagHelperBinding.Descriptors; - foreach (var descriptor in descriptors) - { - var typeName = descriptor.GetTypeName(); - var createTagHelper = new CreateTagHelperIntermediateNode() - { - TagHelperTypeName = typeName, - Descriptor = descriptor - }; - - _builder.Add(createTagHelper); - } - } - private void AddTagHelperAttributes(IList attributes, TagHelperBinding tagHelperBinding) { var descriptors = tagHelperBinding.Descriptors; @@ -727,18 +692,16 @@ namespace Microsoft.AspNetCore.Razor.Language foreach (var associatedDescriptor in associatedDescriptors) { - var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First( - attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor)); - var tagHelperTypeName = associatedDescriptor.GetTypeName(); - var attributePropertyName = associatedAttributeDescriptor.GetPropertyName(); - - var setTagHelperProperty = new SetTagHelperPropertyIntermediateNode() + var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a => + { + return TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, a); + }); + + var setTagHelperProperty = new TagHelperPropertyIntermediateNode() { - PropertyName = attributePropertyName, AttributeName = attribute.Name, - TagHelperTypeName = tagHelperTypeName, - Descriptor = associatedAttributeDescriptor, - Binding = tagHelperBinding, + BoundAttribute = associatedAttributeDescriptor, + TagHelper = associatedDescriptor, AttributeStructure = attribute.AttributeStructure, Source = BuildSourceSpanFromNode(attributeValueNode), IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attribute.Name, associatedAttributeDescriptor), @@ -751,9 +714,9 @@ namespace Microsoft.AspNetCore.Razor.Language } else { - var addHtmlAttribute = new AddTagHelperHtmlAttributeIntermediateNode() + var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode() { - Name = attribute.Name, + AttributeName = attribute.Name, AttributeStructure = attribute.AttributeStructure }; diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultTagHelperDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultTagHelperDescriptorBuilder.cs index 040a957e15..f7500b9a3f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultTagHelperDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultTagHelperDescriptorBuilder.cs @@ -24,6 +24,10 @@ namespace Microsoft.AspNetCore.Razor.Language AssemblyName = assemblyName; _metadata = new Dictionary(StringComparer.Ordinal); + + // Tells code generation that these tag helpers are compatible with ITagHelper. + // For now that's all we support. + _metadata.Add(TagHelperMetadata.Runtime.Name, TagHelperConventions.DefaultKind); } public override string Name { get; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs b/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs index 06865578a1..517f3c959b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs @@ -139,11 +139,6 @@ namespace Microsoft.AspNetCore.Razor.Language _namespace.Insert(i + 1, node); } - public override void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIntermediateNode node) - { - _class.Insert(0, node); - } - public override void VisitDefault(IntermediateNode node) { if (node is MemberDeclarationIntermediateNode) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperBodyIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperBodyIntermediateNode.cs new file mode 100644 index 0000000000..a6853823d5 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperBodyIntermediateNode.cs @@ -0,0 +1,70 @@ +// 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.Razor.Language.Extensions +{ + public sealed class DefaultTagHelperBodyIntermediateNode : ExtensionIntermediateNode + { + public override IntermediateNodeCollection Children { get; } = new DefaultIntermediateNodeCollection(); + + public DefaultTagHelperBodyIntermediateNode() + { + } + + public DefaultTagHelperBodyIntermediateNode(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 void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(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 extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteTagHelperBody(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperCreateIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperCreateIntermediateNode.cs new file mode 100644 index 0000000000..8742f07418 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperCreateIntermediateNode.cs @@ -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 Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Language.Extensions +{ + public sealed class DefaultTagHelperCreateIntermediateNode : ExtensionIntermediateNode + { + public override IntermediateNodeCollection Children { get; } = ReadOnlyIntermediateNodeCollection.Instance; + + public string Field { get; set; } + + public TagHelperDescriptor TagHelper { get; set; } + + public string Type { get; set; } + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(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 extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteTagHelperCreate(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperExecuteIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperExecuteIntermediateNode.cs new file mode 100644 index 0000000000..bde4e4a634 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperExecuteIntermediateNode.cs @@ -0,0 +1,46 @@ +// 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.Razor.Language.Extensions +{ + public sealed class DefaultTagHelperExecuteIntermediateNode : ExtensionIntermediateNode + { + public override IntermediateNodeCollection Children { get; } = ReadOnlyIntermediateNodeCollection.Instance; + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(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 extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteTagHelperExecute(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperHtmlAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperHtmlAttributeIntermediateNode.cs new file mode 100644 index 0000000000..dd44dac55d --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperHtmlAttributeIntermediateNode.cs @@ -0,0 +1,76 @@ +// 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.Razor.Language.Extensions +{ + public sealed class DefaultTagHelperHtmlAttributeIntermediateNode : ExtensionIntermediateNode + { + public DefaultTagHelperHtmlAttributeIntermediateNode() + { + } + + public DefaultTagHelperHtmlAttributeIntermediateNode(TagHelperHtmlAttributeIntermediateNode htmlAttributeNode) + { + if (htmlAttributeNode == null) + { + throw new ArgumentNullException(nameof(htmlAttributeNode)); + } + + AttributeName = htmlAttributeNode.AttributeName; + AttributeStructure = htmlAttributeNode.AttributeStructure; + Source = htmlAttributeNode.Source; + + for (var i = 0; i < htmlAttributeNode.Children.Count; i++) + { + Children.Add(htmlAttributeNode.Children[i]); + } + + for (var i = 0; i < htmlAttributeNode.Diagnostics.Count; i++) + { + Diagnostics.Add(htmlAttributeNode.Diagnostics[i]); + } + } + + public string AttributeName { get; set; } + + public AttributeStructure AttributeStructure { get; set; } + + public override IntermediateNodeCollection Children { get; } = new DefaultIntermediateNodeCollection(); + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(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 extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteTagHelperHtmlAttribute(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperOptimizationPass.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperOptimizationPass.cs new file mode 100644 index 0000000000..cf860e18a8 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperOptimizationPass.cs @@ -0,0 +1,255 @@ +// 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.Intermediate; + +namespace Microsoft.AspNetCore.Razor.Language.Extensions +{ + internal class DefaultTagHelperOptimizationPass : IntermediateNodePassBase, IRazorOptimizationPass + { + // Run later than default order for user code so other passes have a chance to modify the + // tag helper nodes. + public override int Order => DefaultFeatureOrder + 1000; + + protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) + { + var @class = documentNode.FindPrimaryClass(); + if (@class == null) + { + // Bail if we can't find a class node, we need to be able to create fields. + return; + } + + var context = new Context(@class); + + // First find all tag helper nodes that require the default tag helper runtime. + // + // This phase lowers the conceptual nodes to default runtime nodes we only care about those. + var tagHelperNodes = documentNode + .FindDescendantNodes() + .Where(IsTagHelperRuntimeNode) + .ToArray(); + + if (tagHelperNodes.Length == 0) + { + // If nothing uses the default runtime then we're done. + return; + } + + AddDefaultRuntime(context); + + // Each tagHelperNode should be rewritten to use the default tag helper runtime. That doesn't necessarily + // mean that all of these tag helpers are the default kind, just that them are compatible with ITagHelper. + for (var i = 0; i < tagHelperNodes.Length; i++) + { + var tagHelperNode = tagHelperNodes[i]; + + RewriteBody(tagHelperNode); + RewriteHtmlAttributes(tagHelperNode); + AddExecute(tagHelperNode); + + // We need to find all of the 'default' kind tag helpers and rewrite their usage site to use the + // extension nodes for the default tag helper runtime (ITagHelper). + foreach (var tagHelper in tagHelperNode.TagHelpers) + { + RewriteUsage(context, tagHelperNode, tagHelper); + } + } + + // Then for each 'default' kind tag helper we need to generate the field that will hold it. + foreach (var tagHelper in context.TagHelpers) + { + AddField(context, tagHelper); + } + } + + private void AddDefaultRuntime(Context context) + { + // We need to insert a node for the field that will hold the tag helper. We've already generated a field name + // at this time and use it for all uses of the same tag helper type. + // + // We also want to preserve the ordering of the nodes for testability. So insert at the end of any existing + // field nodes. + var i = 0; + while (i < context.Class.Children.Count && context.Class.Children[i] is FieldDeclarationIntermediateNode) + { + i++; + } + + context.Class.Children.Insert(i, new DefaultTagHelperRuntimeIntermediateNode()); + } + + private void RewriteBody(TagHelperIntermediateNode node) + { + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is TagHelperBodyIntermediateNode bodyNode) + { + // We only expect one body node. + node.Children[i] = new DefaultTagHelperBodyIntermediateNode(bodyNode); + break; + } + } + } + + private void AddExecute(TagHelperIntermediateNode node) + { + // Execute the tag helpers at the end, before leaving scope. + node.Children.Add(new DefaultTagHelperExecuteIntermediateNode()); + } + + private void RewriteHtmlAttributes(TagHelperIntermediateNode node) + { + // We need to rewrite each html attribute, so that it will get added to the execution context. + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlAttributeNode) + { + node.Children[i] = new DefaultTagHelperHtmlAttributeIntermediateNode(htmlAttributeNode); + } + } + } + + private void RewriteUsage(Context context, TagHelperIntermediateNode node, TagHelperDescriptor tagHelper) + { + if (!tagHelper.IsDefaultKind()) + { + return; + } + + context.Add(tagHelper); + + // First we need to insert a node for the creation of the tag helper, and the hook up to the execution + // context. This should come after the body node and any existing create nodes. + // + // If we're dealing with something totally malformed, then we'll end up just inserting at the end, and that's not + // so bad. + var i = 0; + + // Find the body node. + while (i < node.Children.Count && node.Children[i] is TagHelperBodyIntermediateNode) + { + i++; + } + while (i < node.Children.Count && node.Children[i] is DefaultTagHelperBodyIntermediateNode) + { + i++; + } + + // Now find the last create node. + while (i < node.Children.Count && node.Children[i] is DefaultTagHelperCreateIntermediateNode) + { + i++; + } + + // Now i has the right insertion point. + node.Children.Insert(i, new DefaultTagHelperCreateIntermediateNode() + { + Field = context.GetFieldName(tagHelper), + TagHelper = tagHelper, + Type = tagHelper.GetTypeName(), + }); + + // Next we need to rewrite any property nodes to use the field and property name for this + // tag helper. + for (i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode && + propertyNode.TagHelper == tagHelper) + { + // This belongs to the current tag helper, replace it. + node.Children[i] = new DefaultTagHelperPropertyIntermediateNode(propertyNode) + { + Field = context.GetFieldName(tagHelper), + Property = propertyNode.BoundAttribute.GetPropertyName(), + }; + } + } + } + + private void AddField(Context context, TagHelperDescriptor tagHelper) + { + // We need to insert a node for the field that will hold the tag helper. We've already generated a field name + // at this time and use it for all uses of the same tag helper type. + // + // We also want to preserve the ordering of the nodes for testability. So insert at the end of any existing + // field nodes. + var i = 0; + while (i < context.Class.Children.Count && context.Class.Children[i] is DefaultTagHelperRuntimeIntermediateNode) + { + i++; + } + + while (i < context.Class.Children.Count && context.Class.Children[i] is FieldDeclarationIntermediateNode) + { + i++; + } + + context.Class.Children.Insert(i, new FieldDeclarationIntermediateNode() + { + Annotations = + { + { CommonAnnotations.DefaultTagHelperExtension.TagHelperField, bool.TrueString }, + }, + Modifiers = + { + "private", + }, + Name = context.GetFieldName(tagHelper), + Type = "global::" + tagHelper.GetTypeName(), + }); + } + + private bool IsTagHelperRuntimeNode(TagHelperIntermediateNode node) + { + foreach (var tagHelper in node.TagHelpers) + { + if (tagHelper.KindUsesDefaultTagHelperRuntime()) + { + return true; + } + } + + return false; + } + + private struct Context + { + private readonly Dictionary _tagHelpers; + + public Context(ClassDeclarationIntermediateNode @class) + { + Class = @class; + + _tagHelpers = new Dictionary(TagHelperDescriptorComparer.Default); + } + + public ClassDeclarationIntermediateNode Class { get; } + + public IEnumerable TagHelpers => _tagHelpers.Keys; + + public bool Add(TagHelperDescriptor tagHelper) + { + if (_tagHelpers.ContainsKey(tagHelper)) + { + return false; + } + + _tagHelpers.Add(tagHelper, GenerateFieldName(tagHelper)); + return true; + } + + public string GetFieldName(TagHelperDescriptor tagHelper) + { + return _tagHelpers[tagHelper]; + } + + private static string GenerateFieldName(TagHelperDescriptor tagHelper) + { + return "__" + tagHelper.GetTypeName().Replace('.', '_'); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperPropertyIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperPropertyIntermediateNode.cs new file mode 100644 index 0000000000..fa2f5af21b --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperPropertyIntermediateNode.cs @@ -0,0 +1,89 @@ +// 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.Razor.Language.Extensions +{ + public sealed class DefaultTagHelperPropertyIntermediateNode : ExtensionIntermediateNode + { + public DefaultTagHelperPropertyIntermediateNode() + { + } + + public DefaultTagHelperPropertyIntermediateNode(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 DefaultIntermediateNodeCollection(); + + public string AttributeName { get; set; } + + public AttributeStructure AttributeStructure { get; set; } + + public BoundAttributeDescriptor BoundAttribute { get; set; } + + public string Field { get; set; } + + public bool IsIndexerNameMatch { get; set; } + + public string Property { get; set; } + + public TagHelperDescriptor TagHelper { get; set; } + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(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 extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteTagHelperProperty(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperRuntimeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperRuntimeIntermediateNode.cs new file mode 100644 index 0000000000..5d37a5debe --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperRuntimeIntermediateNode.cs @@ -0,0 +1,46 @@ +// 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.Razor.Language.Extensions +{ + public sealed class DefaultTagHelperRuntimeIntermediateNode : ExtensionIntermediateNode + { + public override IntermediateNodeCollection Children { get; } = new DefaultIntermediateNodeCollection(); + + public override void Accept(IntermediateNodeVisitor visitor) + { + if (visitor == null) + { + throw new ArgumentNullException(nameof(visitor)); + } + + AcceptExtensionNode(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 extension = target.GetExtension(); + if (extension == null) + { + ReportMissingCodeTargetExtension(context); + return; + } + + extension.WriteTagHelperRuntime(context, this); + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs new file mode 100644 index 0000000000..595593366a --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/DefaultTagHelperTargetExtension.cs @@ -0,0 +1,584 @@ +// 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.Globalization; +using System.Linq; +using System.Text; +using Microsoft.AspNetCore.Razor.Language.CodeGeneration; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.AspNetCore.Razor.Language.Legacy; + +namespace Microsoft.AspNetCore.Razor.Language.Extensions +{ + internal sealed class DefaultTagHelperTargetExtension : IDefaultTagHelperTargetExtension + { + private static readonly string[] PrivateModifiers = new string[] { "private" }; + + public bool DesignTime { get; set; } + + public string RunnerVariableName { get; set; } = "__tagHelperRunner"; + + public string StringValueBufferVariableName { get; set; } = "__tagHelperStringValueBuffer"; + + public string CreateTagHelperMethodName { get; set; } = "CreateTagHelper"; + + public string ExecutionContextTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext"; + + public string ExecutionContextVariableName { get; set; } = "__tagHelperExecutionContext"; + + public string ExecutionContextAddMethodName { get; set; } = "Add"; + + public string TagHelperRunnerTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner"; + + public string ExecutionContextOutputPropertyName { get; set; } = "Output"; + + public string ExecutionContextSetOutputContentAsyncMethodName { get; set; } = "SetOutputContentAsync"; + + public string ExecutionContextAddHtmlAttributeMethodName { get; set; } = "AddHtmlAttribute"; + + public string ExecutionContextAddTagHelperAttributeMethodName { get; set; } = "AddTagHelperAttribute"; + + public string RunnerRunAsyncMethodName { get; set; } = "RunAsync"; + + public string ScopeManagerTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager"; + + public string ScopeManagerVariableName { get; set; } = "__tagHelperScopeManager"; + + public string ScopeManagerBeginMethodName { get; set; } = "Begin"; + + public string ScopeManagerEndMethodName { get; set; } = "End"; + + public string StartTagHelperWritingScopeMethodName { get; set; } = "StartTagHelperWritingScope"; + + public string EndTagHelperWritingScopeMethodName { get; set; } = "EndTagHelperWritingScope"; + + public string TagModeTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.TagHelpers.TagMode"; + + public string HtmlAttributeValueStyleTypeName { get; set; } = "global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle"; + + public string TagHelperOutputIsContentModifiedPropertyName { get; set; } = "IsContentModified"; + + public string BeginAddHtmlAttributeValuesMethodName { get; set; } = "BeginAddHtmlAttributeValues"; + + public string EndAddHtmlAttributeValuesMethodName { get; set; } = "EndAddHtmlAttributeValues"; + + public string BeginWriteTagHelperAttributeMethodName { get; set; } = "BeginWriteTagHelperAttribute"; + + public string EndWriteTagHelperAttributeMethodName { get; set; } = "EndWriteTagHelperAttribute"; + + public string MarkAsHtmlEncodedMethodName { get; set; } = "Html.Raw"; + + public string FormatInvalidIndexerAssignmentMethodName { get; set; } = "InvalidTagHelperIndexerAssignment"; + + public string WriteTagHelperOutputMethod { get; set; } = "Write"; + + public void WriteTagHelperBody(CodeRenderingContext context, DefaultTagHelperBodyIntermediateNode node) + { + if (DesignTime) + { + context.RenderChildren(node); + } + else + { + // Call into the tag helper scope manager to start a new tag helper scope. + // Also capture the value as the current execution context. + context.CodeWriter + .WriteStartAssignment(ExecutionContextVariableName) + .WriteStartInstanceMethodInvocation( + ScopeManagerVariableName, + ScopeManagerBeginMethodName); + + // Assign a unique ID for this instance of the source HTML tag. This must be unique + // per call site, e.g. if the tag is on the view twice, there should be two IDs. + var uniqueId = (string)context.Items[CodeRenderingContext.SuppressUniqueIds]; + if (uniqueId == null) + { + uniqueId = Guid.NewGuid().ToString("N"); + } + + context.CodeWriter.WriteStringLiteral(context.TagHelperRenderingContext.TagName) + .WriteParameterSeparator() + .Write(TagModeTypeName) + .Write(".") + .Write(context.TagHelperRenderingContext.TagMode.ToString()) + .WriteParameterSeparator() + .WriteStringLiteral(uniqueId) + .WriteParameterSeparator(); + + // We remove and redirect writers so TagHelper authors can retrieve content. + using (context.Push(new RuntimeNodeWriter())) + using (context.Push(new RuntimeTagHelperWriter())) + { + using (context.CodeWriter.BuildAsyncLambda()) + { + context.RenderChildren(node); + } + } + + context.CodeWriter.WriteEndMethodInvocation(); + } + } + + public void WriteTagHelperCreate(CodeRenderingContext context, DefaultTagHelperCreateIntermediateNode node) + { + context.CodeWriter + .WriteStartAssignment(node.Field) + .Write(CreateTagHelperMethodName) + .WriteLine("();"); + + if (!DesignTime) + { + context.CodeWriter.WriteInstanceMethodInvocation( + ExecutionContextVariableName, + ExecutionContextAddMethodName, + node.Field); + } + } + + public void WriteTagHelperExecute(CodeRenderingContext context, DefaultTagHelperExecuteIntermediateNode node) + { + if (!DesignTime) + { + context.CodeWriter + .Write("await ") + .WriteStartInstanceMethodInvocation( + RunnerVariableName, + RunnerRunAsyncMethodName) + .Write(ExecutionContextVariableName) + .WriteEndMethodInvocation(); + + var tagHelperOutputAccessor = $"{ExecutionContextVariableName}.{ExecutionContextOutputPropertyName}"; + + context.CodeWriter + .Write("if (!") + .Write(tagHelperOutputAccessor) + .Write(".") + .Write(TagHelperOutputIsContentModifiedPropertyName) + .WriteLine(")"); + + using (context.CodeWriter.BuildScope()) + { + context.CodeWriter + .Write("await ") + .WriteInstanceMethodInvocation( + ExecutionContextVariableName, + ExecutionContextSetOutputContentAsyncMethodName); + } + + context.CodeWriter + .WriteStartMethodInvocation(WriteTagHelperOutputMethod) + .Write(tagHelperOutputAccessor) + .WriteEndMethodInvocation() + .WriteStartAssignment(ExecutionContextVariableName) + .WriteInstanceMethodInvocation( + ScopeManagerVariableName, + ScopeManagerEndMethodName); + } + } + + public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, DefaultTagHelperHtmlAttributeIntermediateNode node) + { + if (DesignTime) + { + context.RenderChildren(node); + } + else + { + var attributeValueStyleParameter = $"{HtmlAttributeValueStyleTypeName}.{node.AttributeStructure}"; + var isConditionalAttributeValue = node.Children.Any( + child => child is CSharpExpressionAttributeValueIntermediateNode || child is CSharpCodeAttributeValueIntermediateNode); + + // All simple text and minimized attributes will be pre-allocated. + if (isConditionalAttributeValue) + { + // Dynamic attribute value should be run through the conditional attribute removal system. It's + // unbound and contains C#. + + // TagHelper attribute rendering is buffered by default. We do not want to write to the current + // writer. + var valuePieceCount = node.Children.Count( + child => + child is HtmlAttributeValueIntermediateNode || + child is CSharpExpressionAttributeValueIntermediateNode || + child is CSharpCodeAttributeValueIntermediateNode || + child is ExtensionIntermediateNode); + + context.CodeWriter + .WriteStartMethodInvocation(BeginAddHtmlAttributeValuesMethodName) + .Write(ExecutionContextVariableName) + .WriteParameterSeparator() + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .Write(valuePieceCount.ToString(CultureInfo.InvariantCulture)) + .WriteParameterSeparator() + .Write(attributeValueStyleParameter) + .WriteEndMethodInvocation(); + + using (context.Push(new TagHelperHtmlAttributeRuntimeNodeWriter())) + { + context.RenderChildren(node); + } + + context.CodeWriter + .WriteMethodInvocation( + EndAddHtmlAttributeValuesMethodName, + ExecutionContextVariableName); + } + else + { + // This is a data-* attribute which includes C#. Do not perform the conditional attribute removal or + // other special cases used when IsDynamicAttributeValue(). But the attribute must still be buffered to + // determine its final value. + + // Attribute value is not plain text, must be buffered to determine its final value. + context.CodeWriter.WriteMethodInvocation(BeginWriteTagHelperAttributeMethodName); + + // We're building a writing scope around the provided chunks which captures everything written from the + // page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to + // ensure we capture all content that's written, directly or indirectly. + using (context.Push(new RuntimeNodeWriter())) + using (context.Push(new RuntimeTagHelperWriter())) + { + context.RenderChildren(node); + } + + context.CodeWriter + .WriteStartAssignment(StringValueBufferVariableName) + .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName) + .WriteStartInstanceMethodInvocation( + ExecutionContextVariableName, + ExecutionContextAddHtmlAttributeMethodName) + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .WriteStartMethodInvocation(MarkAsHtmlEncodedMethodName) + .Write(StringValueBufferVariableName) + .WriteEndMethodInvocation(endLine: false) + .WriteParameterSeparator() + .Write(attributeValueStyleParameter) + .WriteEndMethodInvocation(); + } + } + } + + public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node) + { + var tagHelperRenderingContext = context.TagHelperRenderingContext; + var propertyName = node.BoundAttribute.GetPropertyName(); + var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, node.Field, node.AttributeName, node.BoundAttribute); + + if (!DesignTime) + { + // Ensure that the property we're trying to set has initialized its dictionary bound properties. + if (node.IsIndexerNameMatch && + tagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelper.GetTypeName()}.{propertyName}")) + { + // Throw a reasonable Exception at runtime if the dictionary property is null. + context.CodeWriter + .Write("if (") + .Write(node.Field) + .Write(".") + .Write(propertyName) + .WriteLine(" == null)"); + using (context.CodeWriter.BuildScope()) + { + // System is in Host.NamespaceImports for all MVC scenarios. No need to generate FullName + // of InvalidOperationException type. + context.CodeWriter + .Write("throw ") + .WriteStartNewObject(nameof(InvalidOperationException)) + .WriteStartMethodInvocation(FormatInvalidIndexerAssignmentMethodName) + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .WriteStringLiteral(node.TagHelper.GetTypeName()) + .WriteParameterSeparator() + .WriteStringLiteral(propertyName) + .WriteEndMethodInvocation(endLine: false) // End of method call + .WriteEndMethodInvocation(); // End of new expression / throw statement + } + } + } + + if (tagHelperRenderingContext.RenderedBoundAttributes.TryGetValue(node.AttributeName, out var previousValueAccessor)) + { + context.CodeWriter + .WriteStartAssignment(propertyValueAccessor) + .Write(previousValueAccessor) + .WriteLine(";"); + + return; + } + else + { + tagHelperRenderingContext.RenderedBoundAttributes[node.AttributeName] = propertyValueAccessor; + } + + if (node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty)) + { + if (DesignTime) + { + context.RenderChildren(node); + + context.CodeWriter.WriteStartAssignment(propertyValueAccessor); + if (node.Children.Count == 1 && node.Children.First() is HtmlContentIntermediateNode htmlNode) + { + var content = GetContent(htmlNode); + context.CodeWriter.WriteStringLiteral(content); + } + else + { + context.CodeWriter.Write("string.Empty"); + } + context.CodeWriter.WriteLine(";"); + } + else + { + context.CodeWriter.WriteMethodInvocation(BeginWriteTagHelperAttributeMethodName); + + using (context.Push(new LiteralRuntimeNodeWriter())) + { + context.RenderChildren(node); + } + + context.CodeWriter + .WriteStartAssignment(StringValueBufferVariableName) + .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName) + .WriteStartAssignment(propertyValueAccessor) + .Write(StringValueBufferVariableName) + .WriteLine(";"); + } + } + else + { + if (DesignTime) + { + var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode; + var valueStart = firstMappedChild?.Source; + + using (context.CodeWriter.BuildLinePragma(node.Source)) + { + var assignmentPrefixLength = propertyValueAccessor.Length + " = ".Length; + if (node.BoundAttribute.IsEnum && + node.Children.Count == 1 && + node.Children.First() is IntermediateToken token && + token.IsCSharp) + { + assignmentPrefixLength += $"global::{node.BoundAttribute.TypeName}.".Length; + + if (valueStart != null) + { + context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context); + } + + context.CodeWriter + .WriteStartAssignment(propertyValueAccessor) + .Write("global::") + .Write(node.BoundAttribute.TypeName) + .Write("."); + } + else + { + if (valueStart != null) + { + context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context); + } + + context.CodeWriter.WriteStartAssignment(propertyValueAccessor); + } + + RenderTagHelperAttributeInline(context, node, node.Source); + + context.CodeWriter.WriteLine(";"); + } + } + else + { + using (context.CodeWriter.BuildLinePragma(node.Source)) + { + context.CodeWriter.WriteStartAssignment(propertyValueAccessor); + + if (node.BoundAttribute.IsEnum && + node.Children.Count == 1 && + node.Children.First() is IntermediateToken token && + token.IsCSharp) + { + context.CodeWriter + .Write("global::") + .Write(node.BoundAttribute.TypeName) + .Write("."); + } + + RenderTagHelperAttributeInline(context, node, node.Source); + + context.CodeWriter.WriteLine(";"); + } + } + } + + if (!DesignTime) + { + // We need to inform the context of the attribute value. + context.CodeWriter + .WriteStartInstanceMethodInvocation( + ExecutionContextVariableName, + ExecutionContextAddTagHelperAttributeMethodName) + .WriteStringLiteral(node.AttributeName) + .WriteParameterSeparator() + .Write(propertyValueAccessor) + .WriteParameterSeparator() + .Write($"global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.{node.AttributeStructure}") + .WriteEndMethodInvocation(); + } + } + + public void WriteTagHelperRuntime(CodeRenderingContext context, DefaultTagHelperRuntimeIntermediateNode node) + { + if (!DesignTime) + { + context.CodeWriter.WriteLine("#line hidden"); + + // Need to disable the warning "X is assigned to but never used." for the value buffer since + // whether it's used depends on how a TagHelper is used. + context.CodeWriter.WriteLine("#pragma warning disable 0414"); + context.CodeWriter.WriteField(PrivateModifiers, "string", StringValueBufferVariableName); + context.CodeWriter.WriteLine("#pragma warning restore 0414"); + + context.CodeWriter.WriteField(PrivateModifiers, ExecutionContextTypeName, ExecutionContextVariableName); + + context.CodeWriter + .Write("private ") + .Write(TagHelperRunnerTypeName) + .Write(" ") + .Write(RunnerVariableName) + .Write(" = new ") + .Write(TagHelperRunnerTypeName) + .WriteLine("();"); + + var backedScopeManageVariableName = "__backed" + ScopeManagerVariableName; + context.CodeWriter + .Write("private ") + .WriteVariableDeclaration( + ScopeManagerTypeName, + backedScopeManageVariableName, + value: null); + + context.CodeWriter + .Write("private ") + .Write(ScopeManagerTypeName) + .Write(" ") + .WriteLine(ScopeManagerVariableName); + + using (context.CodeWriter.BuildScope()) + { + context.CodeWriter.WriteLine("get"); + using (context.CodeWriter.BuildScope()) + { + context.CodeWriter + .Write("if (") + .Write(backedScopeManageVariableName) + .WriteLine(" == null)"); + + using (context.CodeWriter.BuildScope()) + { + context.CodeWriter + .WriteStartAssignment(backedScopeManageVariableName) + .WriteStartNewObject(ScopeManagerTypeName) + .Write(StartTagHelperWritingScopeMethodName) + .WriteParameterSeparator() + .Write(EndTagHelperWritingScopeMethodName) + .WriteEndMethodInvocation(); + } + + context.CodeWriter + .Write("return ") + .Write(backedScopeManageVariableName) + .WriteLine(";"); + } + } + } + } + + private void RenderTagHelperAttributeInline( + CodeRenderingContext context, + DefaultTagHelperPropertyIntermediateNode property, + SourceSpan? span) + { + for (var i = 0; i < property.Children.Count; i++) + { + RenderTagHelperAttributeInline(context, property, property.Children[i], span); + } + } + + private void RenderTagHelperAttributeInline( + CodeRenderingContext context, + DefaultTagHelperPropertyIntermediateNode property, + IntermediateNode node, + SourceSpan? span) + { + if (node is CSharpExpressionIntermediateNode || node is HtmlContentIntermediateNode) + { + for (var i = 0; i < node.Children.Count; i++) + { + RenderTagHelperAttributeInline(context, property, node.Children[i], span); + } + } + else if (node is IntermediateToken token) + { + if (DesignTime && node.Source != null) + { + context.AddLineMappingFor(node); + } + + context.CodeWriter.Write(token.Content); + } + else if (node is CSharpCodeIntermediateNode) + { + var error = new RazorError( + LegacyResources.TagHelpers_CodeBlocks_NotSupported_InAttributes, + SourceLocation.FromSpan(span), + span == null ? -1 : span.Value.Length); + context.Diagnostics.Add(RazorDiagnostic.Create(error)); + } + else if (node is TemplateIntermediateNode) + { + var expectedTypeName = property.IsIndexerNameMatch ? property.BoundAttribute.IndexerTypeName : property.BoundAttribute.TypeName; + var error = new RazorError( + LegacyResources.FormatTagHelpers_InlineMarkupBlocks_NotSupported_InAttributes(expectedTypeName), + SourceLocation.FromSpan(span), + span == null ? -1 : span.Value.Length); + context.Diagnostics.Add(RazorDiagnostic.Create(error)); + } + } + + private string GetContent(HtmlContentIntermediateNode node) + { + var builder = new StringBuilder(); + for (var i = 0; i < node.Children.Count; i++) + { + if (node.Children[i] is IntermediateToken token && token.IsHtml) + { + builder.Append(token.Content); + } + } + + return builder.ToString(); + } + + private static string GetTagHelperPropertyAccessor( + bool isIndexerNameMatch, + string tagHelperVariableName, + string attributeName, + BoundAttributeDescriptor descriptor) + { + var propertyAccessor = $"{tagHelperVariableName}.{descriptor.GetPropertyName()}"; + + if (isIndexerNameMatch) + { + var dictionaryKey = attributeName.Substring(descriptor.IndexerNamePrefix.Length); + propertyAccessor += $"[\"{dictionaryKey}\"]"; + } + + return propertyAccessor; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/IDefaultTagHelperTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/IDefaultTagHelperTargetExtension.cs new file mode 100644 index 0000000000..1f7cf9beb4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/IDefaultTagHelperTargetExtension.cs @@ -0,0 +1,22 @@ +// 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.CodeGeneration; + +namespace Microsoft.AspNetCore.Razor.Language.Extensions +{ + public interface IDefaultTagHelperTargetExtension : ICodeTargetExtension + { + void WriteTagHelperBody(CodeRenderingContext context, DefaultTagHelperBodyIntermediateNode node); + + void WriteTagHelperCreate(CodeRenderingContext context, DefaultTagHelperCreateIntermediateNode node); + + void WriteTagHelperExecute(CodeRenderingContext context, DefaultTagHelperExecuteIntermediateNode node); + + void WriteTagHelperHtmlAttribute(CodeRenderingContext context, DefaultTagHelperHtmlAttributeIntermediateNode node); + + void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node); + + void WriteTagHelperRuntime(CodeRenderingContext context, DefaultTagHelperRuntimeIntermediateNode node); + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/IPreallocatedAttributeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/IPreallocatedAttributeTargetExtension.cs index 322e1fc400..3a3dce2f06 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/IPreallocatedAttributeTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/IPreallocatedAttributeTargetExtension.cs @@ -2,18 +2,17 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Razor.Language.CodeGeneration; -using Microsoft.AspNetCore.Razor.Language.Extensions; namespace Microsoft.AspNetCore.Razor.Language.Extensions { internal interface IPreallocatedAttributeTargetExtension : ICodeTargetExtension { - void WriteDeclarePreallocatedTagHelperHtmlAttribute(CodeRenderingContext context, DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode node); + void WriteTagHelperHtmlAttribute(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeIntermediateNode node); - void WriteAddPreallocatedTagHelperHtmlAttribute(CodeRenderingContext context, AddPreallocatedTagHelperHtmlAttributeIntermediateNode node); + void WriteTagHelperHtmlAttributeValue(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeValueIntermediateNode node); - void WriteDeclarePreallocatedTagHelperAttribute(CodeRenderingContext context, DeclarePreallocatedTagHelperAttributeIntermediateNode node); + void WriteTagHelperProperty(CodeRenderingContext context, PreallocatedTagHelperPropertyIntermediateNode node); - void WriteSetPreallocatedTagHelperProperty(CodeRenderingContext context, SetPreallocatedTagHelperPropertyIntermediateNode node); + void WriteTagHelperPropertyValue(CodeRenderingContext context, PreallocatedTagHelperPropertyValueIntermediateNode node); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedAttributeTargetExtension.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedAttributeTargetExtension.cs index 0566a0881f..ded37ecce3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedAttributeTargetExtension.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedAttributeTargetExtension.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions public string FormatInvalidIndexerAssignmentMethodName { get; set; } = "InvalidTagHelperIndexerAssignment"; - public void WriteDeclarePreallocatedTagHelperHtmlAttribute(CodeRenderingContext context, DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode node) + public void WriteTagHelperHtmlAttributeValue(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeValueIntermediateNode node) { context.CodeWriter .Write("private static readonly global::") @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .Write(node.VariableName) .Write(" = ") .WriteStartNewObject("global::" + TagHelperAttributeTypeName) - .WriteStringLiteral(node.Name); + .WriteStringLiteral(node.AttributeName); if (node.AttributeStructure == AttributeStructure.Minimized) { @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions } } - public void WriteAddPreallocatedTagHelperHtmlAttribute(CodeRenderingContext context, AddPreallocatedTagHelperHtmlAttributeIntermediateNode node) + public void WriteTagHelperHtmlAttribute(CodeRenderingContext context, PreallocatedTagHelperHtmlAttributeIntermediateNode node) { context.CodeWriter .WriteStartInstanceMethodInvocation(ExecutionContextVariableName, ExecutionContextAddHtmlAttributeMethodName) @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .WriteEndMethodInvocation(); } - public void WriteDeclarePreallocatedTagHelperAttribute(CodeRenderingContext context, DeclarePreallocatedTagHelperAttributeIntermediateNode node) + public void WriteTagHelperPropertyValue(CodeRenderingContext context, PreallocatedTagHelperPropertyValueIntermediateNode node) { context.CodeWriter .Write("private static readonly global::") @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .Write(node.VariableName) .Write(" = ") .WriteStartNewObject("global::" + TagHelperAttributeTypeName) - .WriteStringLiteral(node.Name) + .WriteStringLiteral(node.AttributeName) .WriteParameterSeparator() .WriteStringLiteral(node.Value) .WriteParameterSeparator() @@ -73,21 +73,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .WriteEndMethodInvocation(); } - public void WriteSetPreallocatedTagHelperProperty(CodeRenderingContext context, SetPreallocatedTagHelperPropertyIntermediateNode node) + public void WriteTagHelperProperty(CodeRenderingContext context, PreallocatedTagHelperPropertyIntermediateNode node) { - var tagHelperVariableName = GetTagHelperVariableName(node.TagHelperTypeName); - var propertyName = node.Descriptor.GetPropertyName(); - var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, tagHelperVariableName, node.AttributeName, node.Descriptor); + var propertyName = node.BoundAttribute.GetPropertyName(); + var propertyValueAccessor = GetTagHelperPropertyAccessor(node.IsIndexerNameMatch, node.Field, node.AttributeName, node.BoundAttribute); var attributeValueAccessor = $"{node.VariableName}.Value" /* ORIGINAL: TagHelperAttributeValuePropertyName */; // Ensure that the property we're trying to set has initialized its dictionary bound properties. if (node.IsIndexerNameMatch && - context.TagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelperTypeName}.{propertyName}")) + context.TagHelperRenderingContext.VerifiedPropertyDictionaries.Add($"{node.TagHelper.GetTypeName()}.{propertyName}")) { // Throw a reasonable Exception at runtime if the dictionary property is null. context.CodeWriter .Write("if (") - .Write(tagHelperVariableName) + .Write(node.Field) .Write(".") .Write(propertyName) .WriteLine(" == null)"); @@ -101,7 +100,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .WriteStartMethodInvocation(FormatInvalidIndexerAssignmentMethodName) .WriteStringLiteral(node.AttributeName) .WriteParameterSeparator() - .WriteStringLiteral(node.TagHelperTypeName) + .WriteStringLiteral(node.TagHelper.GetTypeName()) .WriteParameterSeparator() .WriteStringLiteral(propertyName) .WriteEndMethodInvocation(endLine: false) // End of method call @@ -119,8 +118,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions .WriteEndMethodInvocation(); } - private static string GetTagHelperVariableName(string tagHelperTypeName) => "__" + tagHelperTypeName.Replace('.', '_'); - private static string GetTagHelperPropertyAccessor( bool isIndexerNameMatch, string tagHelperVariableName, diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs index 8171e9a494..ed9488da20 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperAttributeOptimizationPass.cs @@ -10,7 +10,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions { internal class PreallocatedTagHelperAttributeOptimizationPass : IntermediateNodePassBase, IRazorOptimizationPass { - public override int Order => DefaultFeatureOrder; + // We want to run after the passes that 'lower' tag helpers. + public override int Order => DefaultFeatureOrder + 1000; protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode) { @@ -18,7 +19,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions walker.VisitDocument(documentNode); } - internal class PreallocatedTagHelperWalker : IntermediateNodeWalker + internal class PreallocatedTagHelperWalker : + IntermediateNodeWalker, + IExtensionIntermediateNodeVisitor, + IExtensionIntermediateNodeVisitor { private const string PreAllocatedAttributeVariablePrefix = "__tagHelperAttribute_"; @@ -34,7 +38,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions VisitDefault(node); } - public override void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIntermediateNode node) + public void VisitExtension(DefaultTagHelperHtmlAttributeIntermediateNode node) { if (node.Children.Count != 1 || !(node.Children.First() is HtmlContentIntermediateNode)) { @@ -44,15 +48,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions var htmlContentNode = node.Children.First() as HtmlContentIntermediateNode; var plainTextValue = GetContent(htmlContentNode); - DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode declaration = null; + PreallocatedTagHelperHtmlAttributeValueIntermediateNode declaration = null; for (var i = 0; i < _classDeclaration.Children.Count; i++) { var current = _classDeclaration.Children[i]; - if (current is DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode existingDeclaration) + if (current is PreallocatedTagHelperHtmlAttributeValueIntermediateNode existingDeclaration) { - if (string.Equals(existingDeclaration.Name, node.Name, StringComparison.Ordinal) && + if (string.Equals(existingDeclaration.AttributeName, node.AttributeName, StringComparison.Ordinal) && string.Equals(existingDeclaration.Value, plainTextValue, StringComparison.Ordinal) && existingDeclaration.AttributeStructure == node.AttributeStructure) { @@ -66,17 +70,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions { var variableCount = _classDeclaration.Children.Count - _variableCountOffset; var preAllocatedAttributeVariableName = PreAllocatedAttributeVariablePrefix + variableCount; - declaration = new DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode + declaration = new PreallocatedTagHelperHtmlAttributeValueIntermediateNode { VariableName = preAllocatedAttributeVariableName, - Name = node.Name, + AttributeName = node.AttributeName, Value = plainTextValue, AttributeStructure = node.AttributeStructure, }; _classDeclaration.Children.Insert(_preallocatedDeclarationCount++, declaration); } - var addPreAllocatedAttribute = new AddPreallocatedTagHelperHtmlAttributeIntermediateNode + var addPreAllocatedAttribute = new PreallocatedTagHelperHtmlAttributeIntermediateNode { VariableName = declaration.VariableName, }; @@ -85,9 +89,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions Parent.Children[nodeIndex] = addPreAllocatedAttribute; } - public override void VisitSetTagHelperProperty(SetTagHelperPropertyIntermediateNode node) + public void VisitExtension(DefaultTagHelperPropertyIntermediateNode node) { - if (!(node.Descriptor.IsStringProperty || (node.IsIndexerNameMatch && node.Descriptor.IsIndexerStringProperty)) || + if (!(node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty)) || node.Children.Count != 1 || !(node.Children.First() is HtmlContentIntermediateNode)) { @@ -97,15 +101,15 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions var htmlContentNode = node.Children.First() as HtmlContentIntermediateNode; var plainTextValue = GetContent(htmlContentNode); - DeclarePreallocatedTagHelperAttributeIntermediateNode declaration = null; + PreallocatedTagHelperPropertyValueIntermediateNode declaration = null; for (var i = 0; i < _classDeclaration.Children.Count; i++) { var current = _classDeclaration.Children[i]; - if (current is DeclarePreallocatedTagHelperAttributeIntermediateNode existingDeclaration) + if (current is PreallocatedTagHelperPropertyValueIntermediateNode existingDeclaration) { - if (string.Equals(existingDeclaration.Name, node.AttributeName, StringComparison.Ordinal) && + if (string.Equals(existingDeclaration.AttributeName, node.AttributeName, StringComparison.Ordinal) && string.Equals(existingDeclaration.Value, plainTextValue, StringComparison.Ordinal) && existingDeclaration.AttributeStructure == node.AttributeStructure) { @@ -119,25 +123,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions { var variableCount = _classDeclaration.Children.Count - _variableCountOffset; var preAllocatedAttributeVariableName = PreAllocatedAttributeVariablePrefix + variableCount; - declaration = new DeclarePreallocatedTagHelperAttributeIntermediateNode + declaration = new PreallocatedTagHelperPropertyValueIntermediateNode() { VariableName = preAllocatedAttributeVariableName, - Name = node.AttributeName, + AttributeName = node.AttributeName, Value = plainTextValue, AttributeStructure = node.AttributeStructure, }; _classDeclaration.Children.Insert(_preallocatedDeclarationCount++, declaration); } - var setPreallocatedProperty = new SetPreallocatedTagHelperPropertyIntermediateNode + var setPreallocatedProperty = new PreallocatedTagHelperPropertyIntermediateNode(node) { VariableName = declaration.VariableName, - AttributeName = node.AttributeName, - TagHelperTypeName = node.TagHelperTypeName, - PropertyName = node.PropertyName, - Descriptor = node.Descriptor, - Binding = node.Binding, - IsIndexerNameMatch = node.IsIndexerNameMatch, }; var nodeIndex = Parent.Children.IndexOf(node); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/AddPreallocatedTagHelperHtmlAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperHtmlAttributeIntermediateNode.cs similarity index 83% rename from src/Microsoft.AspNetCore.Razor.Language/Extensions/AddPreallocatedTagHelperHtmlAttributeIntermediateNode.cs rename to src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperHtmlAttributeIntermediateNode.cs index d302b5b30d..fc848e872b 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/AddPreallocatedTagHelperHtmlAttributeIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperHtmlAttributeIntermediateNode.cs @@ -7,10 +7,10 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.Extensions { - internal sealed class AddPreallocatedTagHelperHtmlAttributeIntermediateNode : ExtensionIntermediateNode + internal sealed class PreallocatedTagHelperHtmlAttributeIntermediateNode : ExtensionIntermediateNode { public override RazorDiagnosticCollection Diagnostics { get; } = ReadOnlyDiagnosticCollection.Instance; - + public override IntermediateNodeCollection Children => ReadOnlyIntermediateNodeCollection.Instance; public override bool HasDiagnostics => false; @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(visitor)); } - AcceptExtensionNode(this, visitor); + AcceptExtensionNode(this, visitor); } public override void WriteNode(CodeTarget target, CodeRenderingContext context) @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions return; } - extension.WriteAddPreallocatedTagHelperHtmlAttribute(context, this); + extension.WriteTagHelperHtmlAttribute(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperHtmlAttributeValueIntermediateNode.cs similarity index 59% rename from src/Microsoft.AspNetCore.Razor.Language/Extensions/DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode.cs rename to src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperHtmlAttributeValueIntermediateNode.cs index 54ad60cb88..8fdac24c98 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperHtmlAttributeValueIntermediateNode.cs @@ -7,17 +7,41 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.Extensions { - internal sealed class DeclarePreallocatedTagHelperHtmlAttributeIntermediateNode : ExtensionIntermediateNode + internal sealed class PreallocatedTagHelperHtmlAttributeValueIntermediateNode : ExtensionIntermediateNode { + public PreallocatedTagHelperHtmlAttributeValueIntermediateNode() + { + } + + public PreallocatedTagHelperHtmlAttributeValueIntermediateNode(DefaultTagHelperHtmlAttributeIntermediateNode htmlAttributeNode) + { + if (htmlAttributeNode == null) + { + throw new ArgumentNullException(nameof(htmlAttributeNode)); + } + + Source = htmlAttributeNode.Source; + + for (var i = 0; i < htmlAttributeNode.Children.Count; i++) + { + Children.Add(htmlAttributeNode.Children[i]); + } + + for (var i = 0; i < htmlAttributeNode.Diagnostics.Count; i++) + { + Diagnostics.Add(htmlAttributeNode.Diagnostics[i]); + } + } + public override RazorDiagnosticCollection Diagnostics { get; } = ReadOnlyDiagnosticCollection.Instance; - + public override IntermediateNodeCollection Children => ReadOnlyIntermediateNodeCollection.Instance; public override bool HasDiagnostics => false; public string VariableName { get; set; } - public string Name { get; set; } + public string AttributeName { get; set; } public string Value { get; set; } @@ -30,7 +54,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(visitor)); } - AcceptExtensionNode(this, visitor); + AcceptExtensionNode(this, visitor); } public override void WriteNode(CodeTarget target, CodeRenderingContext context) @@ -52,7 +76,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions return; } - extension.WriteDeclarePreallocatedTagHelperHtmlAttribute(context, this); + extension.WriteTagHelperHtmlAttributeValue(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/SetPreallocatedTagHelperPropertyIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperPropertyIntermediateNode.cs similarity index 55% rename from src/Microsoft.AspNetCore.Razor.Language/Extensions/SetPreallocatedTagHelperPropertyIntermediateNode.cs rename to src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperPropertyIntermediateNode.cs index 6635a6fce7..080c5e8210 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/SetPreallocatedTagHelperPropertyIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperPropertyIntermediateNode.cs @@ -7,28 +7,51 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.Extensions { - internal sealed class SetPreallocatedTagHelperPropertyIntermediateNode : ExtensionIntermediateNode + internal sealed class PreallocatedTagHelperPropertyIntermediateNode : ExtensionIntermediateNode { + public PreallocatedTagHelperPropertyIntermediateNode() + { + } + + public PreallocatedTagHelperPropertyIntermediateNode(DefaultTagHelperPropertyIntermediateNode propertyNode) + { + if (propertyNode == null) + { + throw new ArgumentNullException(nameof(propertyNode)); + } + + AttributeName = propertyNode.AttributeName; + AttributeStructure = propertyNode.AttributeStructure; + BoundAttribute = propertyNode.BoundAttribute; + Field = propertyNode.Field; + IsIndexerNameMatch = propertyNode.IsIndexerNameMatch; + Property = propertyNode.Property; + Source = propertyNode.Source; + TagHelper = propertyNode.TagHelper; + } + public override RazorDiagnosticCollection Diagnostics => ReadOnlyDiagnosticCollection.Instance; - + public override IntermediateNodeCollection Children => ReadOnlyIntermediateNodeCollection.Instance; public override bool HasDiagnostics => false; - public string VariableName { get; set; } - public string AttributeName { get; set; } - public string TagHelperTypeName { get; set; } + public AttributeStructure AttributeStructure { get; set; } - public string PropertyName { get; set; } + public BoundAttributeDescriptor BoundAttribute { get; set; } - public BoundAttributeDescriptor Descriptor { get; set; } - - public TagHelperBinding Binding { get; set; } + public string Field { get; set; } public bool IsIndexerNameMatch { get; set; } + public string Property { get; set; } + + public TagHelperDescriptor TagHelper { get; set; } + + public string VariableName { get; set; } + public override void Accept(IntermediateNodeVisitor visitor) { if (visitor == null) @@ -36,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(visitor)); } - AcceptExtensionNode(this, visitor); + AcceptExtensionNode(this, visitor); } public override void WriteNode(CodeTarget target, CodeRenderingContext context) @@ -58,7 +81,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions return; } - extension.WriteSetPreallocatedTagHelperProperty(context, this); + extension.WriteTagHelperProperty(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DeclarePreallocatedTagHelperAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperPropertyValueIntermediateNode.cs similarity index 82% rename from src/Microsoft.AspNetCore.Razor.Language/Extensions/DeclarePreallocatedTagHelperAttributeIntermediateNode.cs rename to src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperPropertyValueIntermediateNode.cs index 4671dd600b..a7f677609d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/DeclarePreallocatedTagHelperAttributeIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/PreallocatedTagHelperPropertyValueIntermediateNode.cs @@ -7,17 +7,17 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate; namespace Microsoft.AspNetCore.Razor.Language.Extensions { - internal sealed class DeclarePreallocatedTagHelperAttributeIntermediateNode : ExtensionIntermediateNode + internal sealed class PreallocatedTagHelperPropertyValueIntermediateNode : ExtensionIntermediateNode { public override RazorDiagnosticCollection Diagnostics { get; } = ReadOnlyDiagnosticCollection.Instance; - + public override IntermediateNodeCollection Children => ReadOnlyIntermediateNodeCollection.Instance; public override bool HasDiagnostics => false; public string VariableName { get; set; } - public string Name { get; set; } + public string AttributeName { get; set; } public string Value { get; set; } @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions throw new ArgumentNullException(nameof(visitor)); } - AcceptExtensionNode(this, visitor); + AcceptExtensionNode(this, visitor); } public override void WriteNode(CodeTarget target, CodeRenderingContext context) @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions return; } - extension.WriteDeclarePreallocatedTagHelperAttribute(context, this); + extension.WriteTagHelperPropertyValue(context, this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Extensions/TemplateIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Extensions/TemplateIntermediateNode.cs index 3a342a02eb..18ccda7fe3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Extensions/TemplateIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Extensions/TemplateIntermediateNode.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Extensions public sealed class TemplateIntermediateNode : ExtensionIntermediateNode { public override IntermediateNodeCollection Children { get; } = new DefaultIntermediateNodeCollection(); - + public override void Accept(IntermediateNodeVisitor visitor) { if (visitor == null) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs index cccd97fb85..fb2f02fc69 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { - internal static class CommonAnnotations + public static class CommonAnnotations { public static readonly object Imported = "Imported"; @@ -12,5 +12,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public static readonly object PrimaryMethod = "PrimaryMethod"; public static readonly object PrimaryNamespace = "PrimaryNamespace"; + + public static class DefaultTagHelperExtension + { + public static readonly object TagHelperField = "TagHelperField"; + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIntermediateNode.cs deleted file mode 100644 index 04b2fa7e4a..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIntermediateNode.cs +++ /dev/null @@ -1,59 +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 System; - -namespace Microsoft.AspNetCore.Razor.Language.Intermediate -{ - public sealed class CreateTagHelperIntermediateNode : IntermediateNode - { - private RazorDiagnosticCollection _diagnostics; - private ItemCollection _annotations; - - public override ItemCollection Annotations - { - get - { - if (_annotations == null) - { - _annotations = new DefaultItemCollection(); - } - - return _annotations; - } - } - - public override RazorDiagnosticCollection Diagnostics - { - get - { - if (_diagnostics == null) - { - _diagnostics = new DefaultRazorDiagnosticCollection(); - } - - return _diagnostics; - } - } - - public override IntermediateNodeCollection Children => ReadOnlyIntermediateNodeCollection.Instance; - - public override SourceSpan? Source { get; set; } - - public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0; - - public string TagHelperTypeName { get; set; } - - public TagHelperDescriptor Descriptor { get; set; } - - public override void Accept(IntermediateNodeVisitor visitor) - { - if (visitor == null) - { - throw new ArgumentNullException(nameof(visitor)); - } - - visitor.VisitCreateTagHelper(this); - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIntermediateNode.cs deleted file mode 100644 index 1695be4b9c..0000000000 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIntermediateNode.cs +++ /dev/null @@ -1,58 +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 System; -using System.Collections.Generic; - -namespace Microsoft.AspNetCore.Razor.Language.Intermediate -{ - public sealed class DeclareTagHelperFieldsIntermediateNode : IntermediateNode - { - private RazorDiagnosticCollection _diagnostics; - private ItemCollection _annotations; - - public override ItemCollection Annotations - { - get - { - if (_annotations == null) - { - _annotations = new DefaultItemCollection(); - } - - return _annotations; - } - } - - public override RazorDiagnosticCollection Diagnostics - { - get - { - if (_diagnostics == null) - { - _diagnostics = new DefaultRazorDiagnosticCollection(); - } - - return _diagnostics; - } - } - - public override IntermediateNodeCollection Children => ReadOnlyIntermediateNodeCollection.Instance; - - public override SourceSpan? Source { get; set; } - - public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0; - - public ISet UsedTagHelperTypeNames { get; set; } = new HashSet(StringComparer.Ordinal); - - public override void Accept(IntermediateNodeVisitor visitor) - { - if (visitor == null) - { - throw new ArgumentNullException(nameof(visitor)); - } - - visitor.VisitDeclareTagHelperFields(this); - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIntermediateNode.cs index 8ab40f86c0..8233f937c1 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIntermediateNode.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0; - public string Name { get; set; } + public string AttributeName { get; set; } public string Prefix { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIntermediateNode.cs index 334a600a39..b491758233 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIntermediateNode.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; namespace Microsoft.AspNetCore.Razor.Language.Intermediate { diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeExtensions.cs index 8105133b9f..708ce3d643 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeExtensions.cs @@ -42,5 +42,35 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate } } } + + public static IReadOnlyList FindDescendantNodes(this IntermediateNode node) + where TNode : IntermediateNode + { + var visitor = new Visitor(); + visitor.Visit(node); + + if (visitor.Results.Count > 0 && visitor.Results[0] == node) + { + // Don't put the node itself in the results + visitor.Results.Remove((TNode)node); + } + + return visitor.Results; + } + + private class Visitor : IntermediateNodeWalker where TNode : IntermediateNode + { + public List Results { get; } = new List(); + + public override void VisitDefault(IntermediateNode node) + { + if (node is TNode match) + { + Results.Add(match); + } + + base.VisitDefault(node); + } + } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeVisitor.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeVisitor.cs index 1879691f0b..bb2c2687a2 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeVisitor.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/IntermediateNodeVisitor.cs @@ -109,11 +109,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate VisitDefault(node); } - public virtual void VisitDeclareTagHelperFields(DeclareTagHelperFieldsIntermediateNode node) - { - VisitDefault(node); - } - public virtual void VisitTagHelper(TagHelperIntermediateNode node) { VisitDefault(node); @@ -124,17 +119,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate VisitDefault(node); } - public virtual void VisitCreateTagHelper(CreateTagHelperIntermediateNode node) + public virtual void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node) { VisitDefault(node); } - public virtual void VisitSetTagHelperProperty(SetTagHelperPropertyIntermediateNode node) - { - VisitDefault(node); - } - - public virtual void VisitAddTagHelperHtmlAttribute(AddTagHelperHtmlAttributeIntermediateNode node) + public virtual void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node) { VisitDefault(node); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperHtmlAttributeIntermediateNode.cs similarity index 89% rename from src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIntermediateNode.cs rename to src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperHtmlAttributeIntermediateNode.cs index 60bd95af6a..3c3040f155 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperHtmlAttributeIntermediateNode.cs @@ -5,7 +5,7 @@ using System; namespace Microsoft.AspNetCore.Razor.Language.Intermediate { - public sealed class AddTagHelperHtmlAttributeIntermediateNode : IntermediateNode + public sealed class TagHelperHtmlAttributeIntermediateNode : IntermediateNode { private RazorDiagnosticCollection _diagnostics; private ItemCollection _annotations; @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0; - public string Name { get; set; } + public string AttributeName { get; set; } public AttributeStructure AttributeStructure { get; set; } @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate throw new ArgumentNullException(nameof(visitor)); } - visitor.VisitAddTagHelperHtmlAttribute(this); + visitor.VisitTagHelperHtmlAttribute(this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIntermediateNode.cs index 440082a918..654d0d540f 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIntermediateNode.cs @@ -47,24 +47,24 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public string TagName { get; set; } public TagMode TagMode { get; set; } - + public ICollection TagHelpers { get; } = new List(); public TagHelperBodyIntermediateNode Body => Children.OfType().SingleOrDefault(); - public IEnumerable SetTagHelperProperties + public IEnumerable Properties { get { - return Children.OfType(); + return Children.OfType(); } } - public IEnumerable AddTagHelperHtmlAttributes + public IEnumerable HtmlAttributes { get { - return Children.OfType(); + return Children.OfType(); } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIntermediateNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperPropertyIntermediateNode.cs similarity index 82% rename from src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIntermediateNode.cs rename to src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperPropertyIntermediateNode.cs index e55541c1f7..148a4b4f81 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIntermediateNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperPropertyIntermediateNode.cs @@ -5,7 +5,7 @@ using System; namespace Microsoft.AspNetCore.Razor.Language.Intermediate { - public sealed class SetTagHelperPropertyIntermediateNode : IntermediateNode + public sealed class TagHelperPropertyIntermediateNode : IntermediateNode { private RazorDiagnosticCollection _diagnostics; private ItemCollection _annotations; @@ -42,17 +42,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public override bool HasDiagnostics => _diagnostics != null && _diagnostics.Count > 0; - public string TagHelperTypeName { get; set; } - - public string PropertyName { get; set; } - public string AttributeName { get; set; } public AttributeStructure AttributeStructure { get; set; } - public BoundAttributeDescriptor Descriptor { get; set; } + public BoundAttributeDescriptor BoundAttribute { get; set; } - public TagHelperBinding Binding { get; set; } + public TagHelperDescriptor TagHelper { get; set; } public bool IsIndexerNameMatch { get; set; } @@ -63,7 +59,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate throw new ArgumentNullException(nameof(visitor)); } - visitor.VisitSetTagHelperProperty(this); + visitor.VisitTagHelperProperty(this); } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/ItemCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/ItemCollection.cs index 914fce3ffd..ef5144a2a3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/ItemCollection.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/ItemCollection.cs @@ -14,6 +14,8 @@ namespace Microsoft.AspNetCore.Razor.Language public abstract bool IsReadOnly { get; } + public abstract void Add(object key, object value); + public abstract void Add(KeyValuePair item); public abstract void Clear(); diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptions.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptions.cs index 601e2cf36b..21568d2bbf 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorCodeGenerationOptions.cs @@ -15,6 +15,11 @@ namespace Microsoft.AspNetCore.Razor.Language return new DefaultRazorCodeGenerationOptions(indentWithTabs: false, indentSize: 4, designTime: false, suppressChecksum: false); } + public static RazorCodeGenerationOptions CreateDesignTimeDefault() + { + return new DefaultRazorCodeGenerationOptions(indentWithTabs: false, indentSize: 4, designTime: true, suppressChecksum: false); + } + public abstract bool DesignTime { get; } public abstract bool IndentWithTabs { get; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs index 8d8e7872f8..bf19f615b3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorDiagnosticFactory.cs @@ -66,6 +66,7 @@ namespace Microsoft.AspNetCore.Razor.Language $"{DiagnosticPrefix}3000", () => Resources.TagHelper_InvalidRestrictedChildNullOrWhitespace, RazorDiagnosticSeverity.Error); + public static RazorDiagnostic CreateTagHelper_InvalidRestrictedChildNullOrWhitespace(string tagHelperDisplayName) { var diagnostic = RazorDiagnostic.Create( diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs index 3095964eb6..a22e6ff02d 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/RazorEngine.cs @@ -72,9 +72,10 @@ namespace Microsoft.AspNetCore.Razor.Language // Intermediate Node Passes builder.Features.Add(new DefaultDocumentClassifierPass()); builder.Features.Add(new DirectiveRemovalOptimizationPass()); + builder.Features.Add(new DefaultTagHelperOptimizationPass()); - // Default Runtime Targets - builder.AddTargetExtension(new PreallocatedAttributeTargetExtension()); + // Default Code Target Extensions + // (currently none) // Default configuration var configurationFeature = new DefaultDocumentClassifierPassFeature(); @@ -104,7 +105,12 @@ namespace Microsoft.AspNetCore.Razor.Language internal static void AddRuntimeDefaults(IRazorEngineBuilder builder) { + // Intermediate Node Passes builder.Features.Add(new PreallocatedTagHelperAttributeOptimizationPass()); + + // Code Target Extensions + builder.AddTargetExtension(new DefaultTagHelperTargetExtension() { DesignTime = false }); + builder.AddTargetExtension(new PreallocatedAttributeTargetExtension()); } internal static void AddDesignTimeDefaults(IRazorEngineBuilder builder) @@ -115,7 +121,8 @@ namespace Microsoft.AspNetCore.Razor.Language // Intermediate Node Passes builder.Features.Add(new DesignTimeDirectivePass()); - // DesignTime Runtime Targets + // Code Target Extensions + builder.AddTargetExtension(new DefaultTagHelperTargetExtension() { DesignTime = true }); builder.AddTargetExtension(new DesignTimeDirectiveTargetExtension()); } diff --git a/src/Microsoft.AspNetCore.Razor.Language/ReadOnlyItemCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/ReadOnlyItemCollection.cs index d26bfdbf2d..beb9200d2a 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/ReadOnlyItemCollection.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/ReadOnlyItemCollection.cs @@ -21,6 +21,16 @@ namespace Microsoft.AspNetCore.Razor.Language public override bool IsReadOnly => true; + public override void Add(object key, object value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + throw new NotSupportedException(); + } + public override void Add(KeyValuePair item) { if (item.Key == null) diff --git a/src/Microsoft.AspNetCore.Razor.Language/SourceLocation.cs b/src/Microsoft.AspNetCore.Razor.Language/SourceLocation.cs index 1563da220e..bb5ac8b973 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/SourceLocation.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/SourceLocation.cs @@ -71,6 +71,20 @@ namespace Microsoft.AspNetCore.Razor.Language /// Set property is only accessible for deserialization purposes. public int CharacterIndex { get; set; } + /// + /// Creates a new instance of from the provided span. + /// + /// + /// The souce span. If null, will be returned. + /// + /// A that corresponds to the beginning of the span. + public static SourceLocation FromSpan(SourceSpan? span) + { + return span == null ? + SourceLocation.Undefined : + new SourceLocation(span.Value.FilePath, span.Value.AbsoluteIndex, span.Value.LineIndex, span.Value.CharacterIndex); + } + /// public override string ToString() { diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperConventions.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperConventions.cs index f2c4e10c72..6e33901d0e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperConventions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperConventions.cs @@ -3,8 +3,8 @@ namespace Microsoft.AspNetCore.Razor.Language { - internal static class TagHelperConventions + public static class TagHelperConventions { - internal const string DefaultKind = "ITagHelper"; + public static readonly string DefaultKind = "ITagHelper"; } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs index e08126cbc2..f1f812d512 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperDescriptorExtensions.cs @@ -7,16 +7,36 @@ namespace Microsoft.AspNetCore.Razor.Language { public static class TagHelperDescriptorExtensions { - public static string GetTypeName(this TagHelperDescriptor descriptor) + public static string GetTypeName(this TagHelperDescriptor tagHelper) { - descriptor.Metadata.TryGetValue(TagHelperMetadata.Common.TypeName, out var typeName); + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + tagHelper.Metadata.TryGetValue(TagHelperMetadata.Common.TypeName, out var typeName); return typeName; } - public static bool IsDefaultKind(this TagHelperDescriptor descriptor) + public static bool IsDefaultKind(this TagHelperDescriptor tagHelper) { - return string.Equals(descriptor.Kind, TagHelperConventions.DefaultKind, StringComparison.Ordinal); + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + + return string.Equals(tagHelper.Kind, TagHelperConventions.DefaultKind, StringComparison.Ordinal); + } + + public static bool KindUsesDefaultTagHelperRuntime(this TagHelperDescriptor tagHelper) + { + if (tagHelper == null) + { + throw new ArgumentNullException(nameof(tagHelper)); + } + + tagHelper.Metadata.TryGetValue(TagHelperMetadata.Runtime.Name, out var value); + return string.Equals(TagHelperConventions.DefaultKind, value, StringComparison.Ordinal); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Razor.Language/TagHelperMetadata.cs b/src/Microsoft.AspNetCore.Razor.Language/TagHelperMetadata.cs index d51b795caa..e53f471a57 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/TagHelperMetadata.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/TagHelperMetadata.cs @@ -3,13 +3,18 @@ namespace Microsoft.AspNetCore.Razor.Language { - internal static class TagHelperMetadata + public static class TagHelperMetadata { public static class Common { - internal const string PropertyName = "Common.PropertyName"; + public static readonly string PropertyName = "Common.PropertyName"; - internal const string TypeName = "Common.TypeName"; + public static readonly string TypeName = "Common.TypeName"; + } + + public static class Runtime + { + public static readonly string Name = "Runtime.Name"; } } } diff --git a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs index e660e4fce6..956c2e0c58 100644 --- a/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs +++ b/src/Microsoft.CodeAnalysis.Razor/DefaultTagHelperDescriptorFactory.cs @@ -57,6 +57,7 @@ namespace Microsoft.CodeAnalysis.Razor var typeName = GetFullName(type); var assemblyName = type.ContainingAssembly.Identity.Name; + var descriptorBuilder = TagHelperDescriptorBuilder.Create(typeName, assemblyName); descriptorBuilder.SetTypeName(typeName); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs index a18fd2b5a3..ee26d8d53b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/InstrumentationPassTest.cs @@ -140,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); - builder.Push(new AddTagHelperHtmlAttributeIntermediateNode()); + builder.Push(new TagHelperHtmlAttributeIntermediateNode()); builder.Push(new CSharpExpressionIntermediateNode() { @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions n, c => { - Assert.IsType(c); + Assert.IsType(c); Children( c, s => CSharpExpression("Hi", s)); @@ -187,7 +187,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions var builder = IntermediateNodeBuilder.Create(document); builder.Push(new TagHelperIntermediateNode()); - builder.Push(new SetTagHelperPropertyIntermediateNode()); + builder.Push(new TagHelperPropertyIntermediateNode()); builder.Push(new CSharpExpressionIntermediateNode() { @@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions n, c => { - Assert.IsType(c); + Assert.IsType(c); Children( c, s => CSharpExpression("Hi", s)); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs index 9dde781d7e..204ecb0272 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/IntegrationTests/CodeGenerationIntegrationTest.cs @@ -547,9 +547,10 @@ public class AllTagHelper : {typeof(TagHelper).FullName} IEnumerable compilationReferences, IEnumerable expectedErrors = null) { - var syntaxTree = CSharpSyntaxTree.ParseText(document.GetCSharpDocument().GeneratedCode); - var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + var cSharp = document.GetCSharpDocument().GeneratedCode; + var syntaxTree = CSharpSyntaxTree.ParseText(cSharp); + var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); var compilation = CSharpCompilation.Create("CodeGenerationTestAssembly", new[] { syntaxTree }, compilationReferences, options); var diagnostics = compilation.GetDiagnostics(); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs index a0d7072407..2a4ca9fc8a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/ModelExpressionPassTest.cs @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions // Assert var tagHelper = FindTagHelperNode(irDocument); - var setProperty = tagHelper.Children.OfType().Single(); + var setProperty = tagHelper.Children.OfType().Single(); var token = Assert.IsType(Assert.Single(setProperty.Children)); Assert.True(token.IsCSharp); @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions // Assert var tagHelper = FindTagHelperNode(irDocument); - var setProperty = tagHelper.Children.OfType().Single(); + var setProperty = tagHelper.Children.OfType().Single(); var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => __model.Bar)", GetCSharpContent(expression)); @@ -132,7 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions // Assert var tagHelper = FindTagHelperNode(irDocument); - var setProperty = tagHelper.Children.OfType().Single(); + var setProperty = tagHelper.Children.OfType().Single(); var expression = Assert.IsType(Assert.Single(setProperty.Children)); Assert.Equal("ModelExpressionProvider.CreateModelExpression(ViewData, __model => Bar)", GetCSharpContent(expression)); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TagHelperDescriptorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TagHelperDescriptorExtensionsTest.cs index 552ac900b4..e9e423853c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TagHelperDescriptorExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TagHelperDescriptorExtensionsTest.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions [Fact] public void IsViewComponentKind_ReturnsFalse_ForNonVCTHDescriptor() { - //Arrange + // Arrange var tagHelper = CreateTagHelperDescriptor(); // Act @@ -62,21 +62,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions private static TagHelperDescriptor CreateTagHelperDescriptor() { - var descriptor = TagHelperDescriptorBuilder.Create("TypeName", "AssemblyName") + var tagHelper = TagHelperDescriptorBuilder.Create("TypeName", "AssemblyName") .TagMatchingRuleDescriptor(rule => rule.RequireTagName("tag-name")) .Build(); - return descriptor; + return tagHelper; } private static TagHelperDescriptor CreateViewComponentTagHelperDescriptor(string name = "ViewComponentName") { - var descriptor = TagHelperDescriptorBuilder.Create(ViewComponentTagHelperConventions.Kind, "TypeName", "AssemblyName") + var tagHelper = TagHelperDescriptorBuilder.Create(ViewComponentTagHelperConventions.Kind, "TypeName", "AssemblyName") .TagMatchingRuleDescriptor(rule => rule.RequireTagName("tag-name")) .AddMetadata(ViewComponentTagHelperMetadata.Name, name) .Build(); - return descriptor; + return tagHelper; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.codegen.cs index 2effa03625..b7599d8445 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.codegen.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.codegen.cs @@ -14,6 +14,7 @@ namespace AspNetCore using Microsoft.AspNetCore.Mvc.ViewFeatures; public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_ModelExpressionTagHelper_cshtml : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage { + private global::InputTestTagHelper __InputTestTagHelper; #pragma warning disable 219 private void __RazorDirectiveTokenHelpers__() { ((System.Action)(() => { @@ -27,7 +28,6 @@ global::System.Object __typeHelper = "InputTestTagHelper, AppCode"; } #pragma warning restore 219 private static System.Object __o = null; - private global::InputTestTagHelper __InputTestTagHelper = null; #pragma warning disable 1998 public async override global::System.Threading.Tasks.Task ExecuteAsync() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.ir.txt index 6952f04369..8fad73433f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.ir.txt +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.ir.txt @@ -11,6 +11,8 @@ Document - UsingDirective - (135:5,1 [40] ) - Microsoft.AspNetCore.Mvc.Rendering UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_ModelExpressionTagHelper_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - + DefaultTagHelperRuntime - + FieldDeclaration - - private - global::InputTestTagHelper - __InputTestTagHelper DesignTimeDirective - DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper DirectiveToken - (294:7,71 [4] ) - Html @@ -29,31 +31,32 @@ Document - DirectiveToken - (33:2,14 [29] ModelExpressionTagHelper.cshtml) - "InputTestTagHelper, AppCode" CSharpCode - IntermediateToken - - CSharp - private static System.Object __o = null; - DeclareTagHelperFields - - InputTestTagHelper MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync HtmlContent - (17:1,0 [2] ModelExpressionTagHelper.cshtml) IntermediateToken - (17:1,0 [2] ModelExpressionTagHelper.cshtml) - Html - \n HtmlContent - (62:2,43 [4] ModelExpressionTagHelper.cshtml) IntermediateToken - (62:2,43 [4] ModelExpressionTagHelper.cshtml) - Html - \n\n TagHelper - (66:4,0 [25] ModelExpressionTagHelper.cshtml) - input-test - TagMode.SelfClosing - TagHelperBody - - CreateTagHelper - - InputTestTagHelper - SetTagHelperProperty - (83:4,17 [4] ModelExpressionTagHelper.cshtml) - for - For - HtmlAttributeValueStyle.DoubleQuotes + DefaultTagHelperBody - + DefaultTagHelperCreate - - InputTestTagHelper + DefaultTagHelperProperty - (83:4,17 [4] ModelExpressionTagHelper.cshtml) - for - Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression InputTestTagHelper.For - HtmlAttributeValueStyle.DoubleQuotes CSharpExpression - IntermediateToken - - CSharp - ModelExpressionProvider.CreateModelExpression(ViewData, __model => IntermediateToken - - CSharp - __model. IntermediateToken - (83:4,17 [4] ModelExpressionTagHelper.cshtml) - CSharp - Date IntermediateToken - - CSharp - ) + DefaultTagHelperExecute - HtmlContent - (91:4,25 [2] ModelExpressionTagHelper.cshtml) IntermediateToken - (91:4,25 [2] ModelExpressionTagHelper.cshtml) - Html - \n TagHelper - (93:5,0 [27] ModelExpressionTagHelper.cshtml) - input-test - TagMode.SelfClosing - TagHelperBody - - CreateTagHelper - - InputTestTagHelper - SetTagHelperProperty - (110:5,17 [6] ModelExpressionTagHelper.cshtml) - for - For - HtmlAttributeValueStyle.DoubleQuotes + DefaultTagHelperBody - + DefaultTagHelperCreate - - InputTestTagHelper + DefaultTagHelperProperty - (110:5,17 [6] ModelExpressionTagHelper.cshtml) - for - Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression InputTestTagHelper.For - HtmlAttributeValueStyle.DoubleQuotes CSharpExpression - IntermediateToken - - CSharp - ModelExpressionProvider.CreateModelExpression(ViewData, __model => IntermediateToken - (111:5,18 [5] ModelExpressionTagHelper.cshtml) - CSharp - Model IntermediateToken - - CSharp - ) + DefaultTagHelperExecute - HtmlContent - (120:5,27 [2] ModelExpressionTagHelper.cshtml) IntermediateToken - (120:5,27 [2] ModelExpressionTagHelper.cshtml) - Html - \n Inject - diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.mappings.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.mappings.txt index cfb4b10107..cb70699613 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.mappings.txt +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_DesignTime.mappings.txt @@ -1,20 +1,20 @@ Source Location: (7:0,7 [8] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper.cshtml) |DateTime| -Generated Location: (889:19,0 [8] ) +Generated Location: (955:20,0 [8] ) |DateTime| Source Location: (33:2,14 [29] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper.cshtml) |"InputTestTagHelper, AppCode"| -Generated Location: (1030:23,37 [29] ) +Generated Location: (1096:24,37 [29] ) |"InputTestTagHelper, AppCode"| Source Location: (83:4,17 [4] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper.cshtml) |Date| -Generated Location: (1675:35,102 [4] ) +Generated Location: (1668:35,102 [4] ) |Date| Source Location: (111:5,18 [5] TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper.cshtml) |Model| -Generated Location: (1991:41,94 [5] ) +Generated Location: (1984:41,94 [5] ) |Model| diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.codegen.cs index 22d7fd62e3..d8b0a457fc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.codegen.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.codegen.cs @@ -16,9 +16,9 @@ namespace AspNetCore { #line hidden #pragma warning disable 0414 - private string __tagHelperStringValueBuffer = null; + private string __tagHelperStringValueBuffer; #pragma warning restore 0414 - private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext = null; + private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext __tagHelperExecutionContext; private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner __tagHelperRunner = new global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner(); private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __backed__tagHelperScopeManager = null; private global::Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperScopeManager __tagHelperScopeManager @@ -32,7 +32,7 @@ namespace AspNetCore return __backed__tagHelperScopeManager; } } - private global::InputTestTagHelper __InputTestTagHelper = null; + private global::InputTestTagHelper __InputTestTagHelper; #pragma warning disable 1998 public async override global::System.Threading.Tasks.Task ExecuteAsync() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.ir.txt index 5fbd77976a..c7a6817e41 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.ir.txt +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/ModelExpressionTagHelper_Runtime.ir.txt @@ -10,7 +10,8 @@ Document - UsingDirective - (135:5,1 [42] ) - Microsoft.AspNetCore.Mvc.Rendering UsingDirective - (178:6,1 [45] ) - Microsoft.AspNetCore.Mvc.ViewFeatures ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_ModelExpressionTagHelper_cshtml - global::Microsoft.AspNetCore.Mvc.Razor.RazorPage - - DeclareTagHelperFields - - InputTestTagHelper + DefaultTagHelperRuntime - + FieldDeclaration - - private - global::InputTestTagHelper - __InputTestTagHelper MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync CSharpCode - IntermediateToken - - CSharp - BeginContext(17, 2, true); @@ -27,14 +28,15 @@ Document - CSharpCode - IntermediateToken - - CSharp - BeginContext(66, 25, false); TagHelper - (66:4,0 [25] ModelExpressionTagHelper.cshtml) - input-test - TagMode.SelfClosing - TagHelperBody - - CreateTagHelper - - InputTestTagHelper - SetTagHelperProperty - (83:4,17 [4] ModelExpressionTagHelper.cshtml) - for - For - HtmlAttributeValueStyle.DoubleQuotes + DefaultTagHelperBody - + DefaultTagHelperCreate - - InputTestTagHelper + DefaultTagHelperProperty - (83:4,17 [4] ModelExpressionTagHelper.cshtml) - for - Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression InputTestTagHelper.For - HtmlAttributeValueStyle.DoubleQuotes CSharpExpression - IntermediateToken - - CSharp - ModelExpressionProvider.CreateModelExpression(ViewData, __model => IntermediateToken - - CSharp - __model. IntermediateToken - (83:4,17 [4] ModelExpressionTagHelper.cshtml) - CSharp - Date IntermediateToken - - CSharp - ) + DefaultTagHelperExecute - CSharpCode - IntermediateToken - - CSharp - EndContext(); CSharpCode - @@ -46,13 +48,14 @@ Document - CSharpCode - IntermediateToken - - CSharp - BeginContext(93, 27, false); TagHelper - (93:5,0 [27] ModelExpressionTagHelper.cshtml) - input-test - TagMode.SelfClosing - TagHelperBody - - CreateTagHelper - - InputTestTagHelper - SetTagHelperProperty - (110:5,17 [6] ModelExpressionTagHelper.cshtml) - for - For - HtmlAttributeValueStyle.DoubleQuotes + DefaultTagHelperBody - + DefaultTagHelperCreate - - InputTestTagHelper + DefaultTagHelperProperty - (110:5,17 [6] ModelExpressionTagHelper.cshtml) - for - Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression InputTestTagHelper.For - HtmlAttributeValueStyle.DoubleQuotes CSharpExpression - IntermediateToken - - CSharp - ModelExpressionProvider.CreateModelExpression(ViewData, __model => IntermediateToken - (111:5,18 [5] ModelExpressionTagHelper.cshtml) - CSharp - Model IntermediateToken - - CSharp - ) + DefaultTagHelperExecute - CSharpCode - IntermediateToken - - CSharp - EndContext(); CSharpCode - diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.codegen.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.codegen.cs index 33ac5a6a4d..1dc43c903c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.codegen.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.codegen.cs @@ -19,6 +19,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages; #line hidden public class TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithoutModel_cshtml : global::Microsoft.AspNetCore.Mvc.RazorPages.Page { + private global::DivTagHelper __DivTagHelper; #pragma warning disable 219 private void __RazorDirectiveTokenHelpers__() { ((System.Action)(() => { @@ -28,7 +29,6 @@ global::System.Object __typeHelper = "*, AppCode"; } #pragma warning restore 219 private static System.Object __o = null; - private global::DivTagHelper __DivTagHelper = null; #pragma warning disable 1998 public async override global::System.Threading.Tasks.Task ExecuteAsync() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.ir.txt b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.ir.txt index 1dc0f9a06c..7979c1a610 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.ir.txt +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Extensions.Test/TestFiles/IntegrationTests/CodeGenerationIntegrationTest/RazorPagesWithoutModel_DesignTime.ir.txt @@ -12,6 +12,8 @@ Document - UsingDirective - (178:6,1 [43] ) - Microsoft.AspNetCore.Mvc.ViewFeatures UsingDirective - (38:3,1 [41] RazorPagesWithoutModel.cshtml) - Microsoft.AspNetCore.Mvc.RazorPages ClassDeclaration - - public - TestFiles_IntegrationTests_CodeGenerationIntegrationTest_RazorPagesWithoutModel_cshtml - global::Microsoft.AspNetCore.Mvc.RazorPages.Page - + DefaultTagHelperRuntime - + FieldDeclaration - - private - global::DivTagHelper - __DivTagHelper DesignTimeDirective - DirectiveToken - (231:7,8 [62] ) - global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper DirectiveToken - (294:7,71 [4] ) - Html @@ -29,7 +31,6 @@ Document - DirectiveToken - (23:2,14 [12] RazorPagesWithoutModel.cshtml) - "*, AppCode" CSharpCode - IntermediateToken - - CSharp - private static System.Object __o = null; - DeclareTagHelperFields - - DivTagHelper MethodDeclaration - - public async override - global::System.Threading.Tasks.Task - ExecuteAsync HtmlContent - (7:1,0 [2] RazorPagesWithoutModel.cshtml) IntermediateToken - (7:1,0 [2] RazorPagesWithoutModel.cshtml) - Html - \n @@ -49,15 +50,16 @@ Document - IntermediateToken - (449:21,43 [1] RazorPagesWithoutModel.cshtml) - Html - > IntermediateToken - (450:21,44 [6] RazorPagesWithoutModel.cshtml) - Html - \n TagHelper - (456:22,4 [31] RazorPagesWithoutModel.cshtml) - div - TagMode.StartTagAndEndTag - TagHelperBody - - CreateTagHelper - - DivTagHelper - AddTagHelperHtmlAttribute - - class - AttributeStructure.DoubleQuotes + DefaultTagHelperBody - + DefaultTagHelperCreate - - DivTagHelper + DefaultTagHelperHtmlAttribute - - class - HtmlAttributeValueStyle.DoubleQuotes HtmlContent - (468:22,16 [11] RazorPagesWithoutModel.cshtml) IntermediateToken - (468:22,16 [11] RazorPagesWithoutModel.cshtml) - Html - text-danger + DefaultTagHelperExecute - HtmlContent - (487:22,35 [6] RazorPagesWithoutModel.cshtml) IntermediateToken - (487:22,35 [6] RazorPagesWithoutModel.cshtml) - Html - \n TagHelper - (493:23,4 [237] RazorPagesWithoutModel.cshtml) - div - TagMode.StartTagAndEndTag - TagHelperBody - + DefaultTagHelperBody - HtmlContent - (517:23,28 [48] RazorPagesWithoutModel.cshtml) IntermediateToken - (517:23,28 [10] RazorPagesWithoutModel.cshtml) - Html - \n IntermediateToken - (527:24,8 [6] RazorPagesWithoutModel.cshtml) - Html -