public void WriteTagHelperProperty_Runtime_NonStringIndexer_RendersCorrectly() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateRuntime(); var tagHelperNode = new TagHelperIntermediateNode(); var node = new DefaultTagHelperPropertyIntermediateNode() { AttributeName = "foo-bound", AttributeStructure = AttributeStructure.DoubleQuotes, BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(), FieldName = "__InputTagHelper", IsIndexerNameMatch = true, PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Source = Span, Children = { new CSharpExpressionIntermediateNode() { Children ={ new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, } } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); // Act extension.WriteTagHelperProperty(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"if (__InputTagHelper.IntIndexer == null) { throw new InvalidOperationException(InvalidTagHelperIndexerAssignment(""foo-bound"", ""InputTagHelper"", ""IntIndexer"")); } #nullable restore #line 3 ""test.cshtml"" __InputTagHelper.IntIndexer[""bound""] = 32; #line default #line hidden #nullable disable __tagHelperExecutionContext.AddTagHelperAttribute(""foo-bound"", __InputTagHelper.IntIndexer[""bound""], global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); ", csharp, ignoreLineEndingDifferences: true); }
public void VisitExtension(DefaultTagHelperPropertyIntermediateNode node) { if (!(node.BoundAttribute.IsStringProperty || (node.IsIndexerNameMatch && node.BoundAttribute.IsIndexerStringProperty)) || node.Children.Count != 1 || !(node.Children.First() is HtmlContentIntermediateNode)) { return; } var htmlContentNode = node.Children.First() as HtmlContentIntermediateNode; var plainTextValue = GetContent(htmlContentNode); PreallocatedTagHelperPropertyValueIntermediateNode declaration = null; for (var i = 0; i < _classDeclaration.Children.Count; i++) { var current = _classDeclaration.Children[i]; if (current is PreallocatedTagHelperPropertyValueIntermediateNode existingDeclaration) { if (string.Equals(existingDeclaration.AttributeName, node.AttributeName, StringComparison.Ordinal) && string.Equals(existingDeclaration.Value, plainTextValue, StringComparison.Ordinal) && existingDeclaration.AttributeStructure == node.AttributeStructure) { declaration = existingDeclaration; break; } } } if (declaration == null) { var variableCount = _classDeclaration.Children.Count - _variableCountOffset; var preAllocatedAttributeVariableName = PreAllocatedAttributeVariablePrefix + variableCount; declaration = new PreallocatedTagHelperPropertyValueIntermediateNode() { VariableName = preAllocatedAttributeVariableName, AttributeName = node.AttributeName, Value = plainTextValue, AttributeStructure = node.AttributeStructure, }; _classDeclaration.Children.Insert(_preallocatedDeclarationCount++, declaration); } var setPreallocatedProperty = new PreallocatedTagHelperPropertyIntermediateNode(node) { VariableName = declaration.VariableName, }; var nodeIndex = Parent.Children.IndexOf(node); Parent.Children[nodeIndex] = setPreallocatedProperty; }
public void WriteTagHelperProperty_DesignTime_NonStringIndexer_RendersCorrectly() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); var node = new DefaultTagHelperPropertyIntermediateNode() { AttributeName = "foo-bound", AttributeStructure = AttributeStructure.DoubleQuotes, BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(), FieldName = "__InputTagHelper", IsIndexerNameMatch = true, PropertyName = "IntIndexer", TagHelper = IntIndexerTagHelper, Source = Span, Children = { new CSharpExpressionIntermediateNode() { Children ={ new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, } } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); // Act extension.WriteTagHelperProperty(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @" #nullable restore #line 3 ""test.cshtml"" __InputTagHelper.IntIndexer[""bound""] = 32; #line default #line hidden #nullable disable ", csharp, ignoreLineEndingDifferences: true); }
public PreallocatedTagHelperPropertyIntermediateNode(DefaultTagHelperPropertyIntermediateNode propertyNode) { if (propertyNode == null) { throw new ArgumentNullException(nameof(propertyNode)); } AttributeName = propertyNode.AttributeName; AttributeStructure = propertyNode.AttributeStructure; BoundAttribute = propertyNode.BoundAttribute; FieldName = propertyNode.FieldName; IsIndexerNameMatch = propertyNode.IsIndexerNameMatch; PropertyName = propertyNode.PropertyName; Source = propertyNode.Source; TagHelper = propertyNode.TagHelper; }
public void WriteTagHelperProperty_Runtime_StringProperty_HtmlContent_RendersCorrectly() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateRuntime(); var tagHelperNode = new TagHelperIntermediateNode(); var node = new DefaultTagHelperPropertyIntermediateNode() { AttributeName = "bound", AttributeStructure = AttributeStructure.DoubleQuotes, BoundAttribute = StringPropertyTagHelper.BoundAttributes.Single(), FieldName = "__InputTagHelper", IsIndexerNameMatch = false, PropertyName = "StringProp", TagHelper = StringPropertyTagHelper, Children = { new HtmlContentIntermediateNode() { Children ={ new IntermediateToken { Kind = TokenKind.Html, Content = "\"value\"", } }, } } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); // Act extension.WriteTagHelperProperty(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); // The attribute value is not rendered inline because we are not using the preallocated writer. Assert.Equal( @"BeginWriteTagHelperAttribute(); Render Children __tagHelperStringValueBuffer = EndWriteTagHelperAttribute(); __InputTagHelper.StringProp = __tagHelperStringValueBuffer; __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.StringProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); ", csharp, ignoreLineEndingDifferences: true); }
public void WriteTagHelperProperty_Runtime_NonStringProperty_RendersCorrectly_WithoutLocation() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateRuntime(); var tagHelperNode = new TagHelperIntermediateNode(); var node = new DefaultTagHelperPropertyIntermediateNode() { AttributeName = "bound", AttributeStructure = AttributeStructure.DoubleQuotes, BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(), FieldName = "__InputTagHelper", IsIndexerNameMatch = false, PropertyName = "IntProp", TagHelper = IntPropertyTagHelper, Children = { new CSharpExpressionIntermediateNode() { Children ={ new IntermediateToken { Kind = TokenKind.CSharp, Content = "32", } }, } } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); // Act extension.WriteTagHelperProperty(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"__InputTagHelper.IntProp = 32; __tagHelperExecutionContext.AddTagHelperAttribute(""bound"", __InputTagHelper.IntProp, global::Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle.DoubleQuotes); ", csharp, ignoreLineEndingDifferences: true); }
[Fact] // We don't actually assign the expression result at design time, we just use string.Empty as a placeholder. public void WriteTagHelperProperty_DesignTime_StringProperty_NonHtmlContent_RendersCorrectly() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateDesignTime(); var tagHelperNode = new TagHelperIntermediateNode(); var node = new DefaultTagHelperPropertyIntermediateNode() { AttributeName = "bound", AttributeStructure = AttributeStructure.DoubleQuotes, BoundAttribute = StringPropertyTagHelper.BoundAttributes.Single(), FieldName = "__InputTagHelper", IsIndexerNameMatch = false, PropertyName = "StringProp", TagHelper = StringPropertyTagHelper, Children = { new CSharpExpressionIntermediateNode() { Children ={ new IntermediateToken { Kind = TokenKind.CSharp, Content = "\"3+5\"", } }, } } }; tagHelperNode.Children.Add(node); Push(context, tagHelperNode); // Act extension.WriteTagHelperProperty(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"Render Children __InputTagHelper.StringProp = string.Empty; ", csharp, ignoreLineEndingDifferences: true); }
public void WriteTagHelperProperty_Runtime_NonStringProperty_SecondUseOfAttribute() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateRuntime(); var tagHelperNode = new TagHelperIntermediateNode(); var node1 = new DefaultTagHelperPropertyIntermediateNode() { // We only look at the attribute name here. AttributeName = "bound", FieldName = "__OtherTagHelper", PropertyName = "IntProp", }; var node2 = new DefaultTagHelperPropertyIntermediateNode() { AttributeName = "bound", AttributeStructure = AttributeStructure.DoubleQuotes, BoundAttribute = IntPropertyTagHelper.BoundAttributes.Single(), FieldName = "__InputTagHelper", IsIndexerNameMatch = false, PropertyName = "IntProp", TagHelper = IntPropertyTagHelper, Source = Span, }; tagHelperNode.Children.Add(node1); tagHelperNode.Children.Add(node2); Push(context, tagHelperNode); // Act extension.WriteTagHelperProperty(context, node2); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"__InputTagHelper.IntProp = __OtherTagHelper.IntProp; ", csharp, ignoreLineEndingDifferences: true); }
public void RenderTagHelperAttributeInline_NonStringIndexerMatch_TemplateInAttribute_Errors() { // Arrange var extension = new DefaultTagHelperTargetExtension(); var context = TestCodeRenderingContext.CreateRuntime(); var node = new DefaultTagHelperPropertyIntermediateNode() { BoundAttribute = IntIndexerTagHelper.BoundAttributes.Single(), IsIndexerNameMatch = true, }; var expectedLocation = new SourceSpan(100, 10); var expectedDiagnostic = RazorDiagnosticFactory.CreateTagHelper_InlineMarkupBlocksNotSupportedInAttributes(expectedLocation, "System.Int32"); // Act extension.RenderTagHelperAttributeInline(context, node, new TemplateIntermediateNode(), expectedLocation); // Assert var diagnostic = Assert.Single(context.Diagnostics); Assert.Equal(expectedDiagnostic, diagnostic); }
public void WriteTagHelperProperty(CodeRenderingContext context, DefaultTagHelperPropertyIntermediateNode node) { var tagHelperNode = context.Parent as TagHelperIntermediateNode; if (context.Parent == null) { var message = Resources.FormatIntermediateNodes_InvalidParentNode(node.GetType(), typeof(TagHelperIntermediateNode)); throw new InvalidOperationException(message); } if (!context.Options.DesignTime) { // Ensure that the property we're trying to set has initialized its dictionary bound properties. if (node.IsIndexerNameMatch && object.ReferenceEquals(FindFirstUseOfIndexer(tagHelperNode, node), node)) { // Throw a reasonable Exception at runtime if the dictionary property is null. context.CodeWriter .Write("if (") .Write(node.FieldName) .Write(".") .Write(node.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(node.PropertyName) .WriteEndMethodInvocation(endLine: false) // End of method call .WriteEndMethodInvocation(); // End of new expression / throw statement } } } // If this is not the first use of the attribute value, we need to evaluate the expression and assign it to // the tag helper property. // // Otherwise, the value has already been computed and assigned to another tag helper. We just need to // copy from that tag helper to this one. // // This is important because we can't evaluate the expression twice due to side-effects. var firstUseOfAttribute = FindFirstUseOfAttribute(tagHelperNode, node); if (!object.ReferenceEquals(firstUseOfAttribute, node)) { // If we get here, this value has already been used. We just need to copy the value. context.CodeWriter .WriteStartAssignment(GetPropertyAccessor(node)) .Write(GetPropertyAccessor(firstUseOfAttribute)) .WriteLine(";"); return; } // If we get there, this is the first time seeing this property so we need to evaluate the expression. if (node.BoundAttribute.ExpectsStringValue(node.AttributeName)) { if (context.Options.DesignTime) { context.RenderChildren(node); context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node)); 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); context.RenderChildren(node, new LiteralRuntimeNodeWriter()); context.CodeWriter .WriteStartAssignment(StringValueBufferVariableName) .WriteMethodInvocation(EndWriteTagHelperAttributeMethodName) .WriteStartAssignment(GetPropertyAccessor(node)) .Write(StringValueBufferVariableName) .WriteLine(";"); } } else { if (context.Options.DesignTime) { var firstMappedChild = node.Children.FirstOrDefault(child => child.Source != null) as IntermediateNode; var valueStart = firstMappedChild?.Source; using (context.CodeWriter.BuildLinePragma(node.Source, context)) { var accessor = GetPropertyAccessor(node); var assignmentPrefixLength = accessor.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(accessor) .Write("global::") .Write(node.BoundAttribute.TypeName) .Write("."); } else { if (valueStart != null) { context.CodeWriter.WritePadding(assignmentPrefixLength, node.Source, context); } context.CodeWriter.WriteStartAssignment(GetPropertyAccessor(node)); } if (node.Children.Count == 0 && node.AttributeStructure == AttributeStructure.Minimized && node.BoundAttribute.ExpectsBooleanValue(node.AttributeName)) { // If this is a minimized boolean attribute, set the value to true. context.CodeWriter.Write("true"); } else { RenderTagHelperAttributeInline(context, node, node.Source); } context.CodeWriter.WriteLine(";"); } }