public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } IDisposable linePragmaScope = null; if (node.Source != null) { linePragmaScope = context.CodeWriter.BuildEnhancedLinePragma(node.Source.Value, context, WriteCSharpExpressionMethod.Length + 1); if (!context.Options.UseEnhancedLinePragma) { context.CodeWriter.WritePadding(WriteCSharpExpressionMethod.Length + 1, node.Source, context); } } context.CodeWriter.WriteStartMethodInvocation(WriteCSharpExpressionMethod); for (var i = 0; i < node.Children.Count; i++) { if (node.Children[i] is IntermediateToken token && token.IsCSharp) { context.CodeWriter.Write(token.Content); }
public void WriteCSharpExpression_SkipsLinePragma_WithoutSource() { // Arrange var writer = new DesignTimeNodeWriter(); var context = TestCodeRenderingContext.CreateDesignTime(); var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); builder.Add(new IntermediateToken() { Content = "i++", Kind = TokenKind.CSharp, }); // Act writer.WriteCSharpExpression(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"__o = i++; ", csharp, ignoreLineEndingDifferences: true); }
public void WriteCSharpExpression_WritesLinePragma_WithSource() { // Arrange var writer = new DesignTimeNodeWriter(); var context = TestCodeRenderingContext.CreateDesignTime(); var node = new CSharpExpressionIntermediateNode() { Source = new SourceSpan("test.cshtml", 0, 0, 0, 3), }; var builder = IntermediateNodeBuilder.Create(node); builder.Add(new IntermediateToken() { Content = "i++", Kind = TokenKind.CSharp, }); // Act writer.WriteCSharpExpression(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"#line 1 ""test.cshtml"" __o = i++; #line default #line hidden ", csharp, ignoreLineEndingDifferences: true); }
public override void VisitCSharpExpression(CSharpExpressionIntermediateNode node) { if (!string.IsNullOrEmpty(_unconsumedHtml)) { // We're in the middle of writing out an element tag. This C# expression might represent an entire // attribute (e.g., @onclick(...)), or it might represent the value of an attribute (e.g., something=@value). // Differentiate based on whether the unconsumed HTML ends with " attribute=". var incompleteAttributeMatch = _incompleteAttributeRegex.Match(_unconsumedHtml); if (incompleteAttributeMatch.Success) { var wholeMatchText = incompleteAttributeMatch.Groups[0]; var attributeName = incompleteAttributeMatch.Groups["name"].Value; _unconsumedHtml = _unconsumedHtml.Substring(0, _unconsumedHtml.Length - wholeMatchText.Length + 1); _nextElementAttributes[attributeName] = node; } else { // There's no incomplete attribute, so the C# expression must represent an entire attribute _nextElementAttributeExpressions.Add(node); } } else { // We're between tags, so treat it as an @someVar expression to be rendered as a text node WriteContentExpression(++_sourceSequence, Context, node); } }
public override void VisitCSharpExpression(CSharpExpressionIntermediateNode node) { if (node.Source != null) { Items.Add(new InstrumentationItem(node, Parent, isLiteral: false, source: node.Source.Value)); } VisitDefault(node); }
public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node) { if (string.Equals(node.BoundAttribute.TypeName, ModelExpressionTypeName, StringComparison.Ordinal) || (node.IsIndexerNameMatch && string.Equals(node.BoundAttribute.IndexerTypeName, ModelExpressionTypeName, StringComparison.Ordinal))) { var expression = new CSharpExpressionIntermediateNode(); expression.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, Content = "ModelExpressionProvider.CreateModelExpression(ViewData, __model => ", }); if (node.Children.Count == 1 && node.Children[0] is IntermediateToken token && token.IsCSharp) { // A 'simple' expression will look like __model => __model.Foo expression.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, Content = "__model." }); expression.Children.Add(token); } else { for (var i = 0; i < node.Children.Count; i++) { if (node.Children[i] is CSharpExpressionIntermediateNode nestedExpression) { for (var j = 0; j < nestedExpression.Children.Count; j++) { if (nestedExpression.Children[j] is IntermediateToken cSharpToken && cSharpToken.IsCSharp) { expression.Children.Add(cSharpToken); } } continue; } } } expression.Children.Add(new IntermediateToken() { Kind = TokenKind.CSharp, Content = ")", }); node.Children.Clear(); node.Children.Add(expression); }
public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node) { var attribute = new ComponentAttributeIntermediateNode(node); _children.Add(attribute); // Since we don't support complex content, we can rewrite the inside of this // node to the rather simpler form that property nodes usually have. for (var i = 0; i < attribute.Children.Count; i++) { if (attribute.Children[i] is HtmlAttributeValueIntermediateNode htmlValue) { var newNode = new HtmlContentIntermediateNode() { Source = htmlValue.Source, }; for (var j = 0; j < htmlValue.Children.Count; j++) { newNode.Children.Add(htmlValue.Children[j]); } attribute.Children[i] = newNode; } else if (attribute.Children[i] is CSharpExpressionAttributeValueIntermediateNode expressionValue) { var newNode = new CSharpExpressionIntermediateNode() { Source = expressionValue.Source, }; for (var j = 0; j < expressionValue.Children.Count; j++) { newNode.Children.Add(expressionValue.Children[j]); } attribute.Children[i] = newNode; } else if (attribute.Children[i] is CSharpCodeAttributeValueIntermediateNode codeValue) { var newNode = new CSharpExpressionIntermediateNode() { Source = codeValue.Source, }; for (var j = 0; j < codeValue.Children.Count; j++) { newNode.Children.Add(codeValue.Children[j]); } attribute.Children[i] = newNode; } } }
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } WriteCSharpExpressionInnards(context, node); }
private CSharpExpressionIntermediateNode MakeCSharpExpressionIntermediateNode(IntermediateNode parent, string csharpExpressionContent) { var content = new CSharpExpressionIntermediateNode { Source = parent.Source }; content.Children.Add(new IntermediateToken { Kind = TokenKind.CSharp, Source = parent.Source, Content = csharpExpressionContent }); return(content); }
public void WriteCSharpExpression_WithSource_WritesPadding() { // Arrange var codeWriter = new CodeWriter(); var writer = new RuntimeNodeWriter() { WriteCSharpExpressionMethod = "Test", }; var context = TestCodeRenderingContext.CreateRuntime(); var node = new CSharpExpressionIntermediateNode() { Source = new SourceSpan("test.cshtml", 8, 0, 8, 3), }; var builder = IntermediateNodeBuilder.Create(node); builder.Add(new IntermediateToken() { Content = "i", Kind = TokenKind.CSharp, }); builder.Add(new MyExtensionIntermediateNode()); builder.Add(new IntermediateToken() { Content = "++", Kind = TokenKind.CSharp, }); // Act writer.WriteCSharpExpression(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @" #nullable restore #line 1 ""test.cshtml"" Test(iRender Children ++); #line default #line hidden #nullable disable ", csharp, ignoreLineEndingDifferences: true); }
private void WriteCSharpExpressionInnards(CodeRenderingContext context, CSharpExpressionIntermediateNode node, string type = null) { if (node.Children.Count == 0) { return; } if (node.Source != null) { using (context.CodeWriter.BuildLinePragma(node.Source.Value, context)) { var offset = DesignTimeVariable.Length + " = ".Length; if (type != null) { offset += type.Length + 2; // two parenthesis } context.CodeWriter.WritePadding(offset, node.Source, context); context.CodeWriter.WriteStartAssignment(DesignTimeVariable); if (type != null) { context.CodeWriter.Write("("); context.CodeWriter.Write(type); context.CodeWriter.Write(")"); } for (var i = 0; i < node.Children.Count; i++) { if (node.Children[i] is IntermediateToken token && token.IsCSharp) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content); } else { // There may be something else inside the expression like a Template or another extension node. context.RenderNode(node.Children[i]); } }
public void WriteCSharpExpression_WithExtensionNode_WritesPadding() { // Arrange var codeWriter = new CodeWriter(); var writer = new RuntimeNodeWriter() { WriteCSharpExpressionMethod = "Test", }; var context = TestCodeRenderingContext.CreateRuntime(); var node = new CSharpExpressionIntermediateNode(); var builder = IntermediateNodeBuilder.Create(node); builder.Add(new IntermediateToken() { Content = "i", Kind = TokenKind.CSharp, }); builder.Add(new MyExtensionIntermediateNode()); builder.Add(new IntermediateToken() { Content = "++", Kind = TokenKind.CSharp, }); // Act writer.WriteCSharpExpression(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @"Test(iRender Children ++); ", csharp, ignoreLineEndingDifferences: true); }
public override void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (node == null) { throw new ArgumentNullException(nameof(node)); } if (node.Children.Count == 0) { return; } if (node.Source != null) { using (context.CodeWriter.BuildLinePragma(node.Source.Value)) { var offset = DesignTimeVariable.Length + " = ".Length; context.CodeWriter.WritePadding(offset, node.Source, context); context.CodeWriter.WriteStartAssignment(DesignTimeVariable); for (var i = 0; i < node.Children.Count; i++) { if (node.Children[i] is IntermediateToken token && token.IsCSharp) { context.AddSourceMappingFor(token); context.CodeWriter.Write(token.Content); } else { // There may be something else inside the expression like a Template or another extension node. context.RenderNode(node.Children[i]); } }
public void WriteCSharpExpression_UsesWriteLiteral_WritesLinePragma_WithSource() { // Arrange var writer = new LiteralRuntimeNodeWriter(); var context = TestCodeRenderingContext.CreateRuntime(); var node = new CSharpExpressionIntermediateNode() { Source = new SourceSpan("test.cshtml", 0, 0, 0, 3, 0, 3), }; var builder = IntermediateNodeBuilder.Create(node); builder.Add(new IntermediateToken() { Content = "i++", Kind = TokenKind.CSharp, }); // Act writer.WriteCSharpExpression(context, node); // Assert var csharp = context.CodeWriter.GenerateCode(); Assert.Equal( @" #nullable restore #line (1,1)-(1,4) 13 ""test.cshtml"" WriteLiteral(i++); #line default #line hidden #nullable disable ", csharp, ignoreLineEndingDifferences: true); }
// CSharp expressions are broken up into blocks and spans because Razor allows Razor comments // inside an expression. // Ex: // @DateTime.@*This is a comment*@Now // // We need to capture this in the IR so that we can give each piece the correct source mappings public override void VisitExpressionBlock(ExpressionChunkGenerator chunkGenerator, Block block) { if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode) { VisitDefault(block); return; } var expressionNode = new CSharpExpressionIntermediateNode(); _builder.Push(expressionNode); VisitDefault(block); _builder.Pop(); if (expressionNode.Children.Count > 0) { var sourceRangeStart = expressionNode .Children .FirstOrDefault(child => child.Source != null) ?.Source; if (sourceRangeStart != null) { var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0); expressionNode.Source = new SourceSpan( sourceRangeStart.Value.FilePath ?? FilePath, sourceRangeStart.Value.AbsoluteIndex, sourceRangeStart.Value.LineIndex, sourceRangeStart.Value.CharacterIndex, contentLength); } } }
public override void VisitCSharpImplicitExpression(CSharpImplicitExpressionSyntax node) { if (_builder.Current is CSharpExpressionAttributeValueIntermediateNode) { base.VisitCSharpImplicitExpression(node); return; } var expressionNode = new CSharpExpressionIntermediateNode(); _builder.Push(expressionNode); base.VisitCSharpImplicitExpression(node); _builder.Pop(); if (expressionNode.Children.Count > 0) { var sourceRangeStart = expressionNode .Children .FirstOrDefault(child => child.Source != null) ?.Source; if (sourceRangeStart != null) { var contentLength = expressionNode.Children.Sum(child => child.Source?.Length ?? 0); expressionNode.Source = new SourceSpan( sourceRangeStart.Value.FilePath ?? SourceDocument.FilePath, sourceRangeStart.Value.AbsoluteIndex, sourceRangeStart.Value.LineIndex, sourceRangeStart.Value.CharacterIndex, contentLength); } } }
private void RewriteUsage(TagHelperIntermediateNode node, int index, ComponentAttributeExtensionNode attributeNode) { // Bind works similarly to a macro, it always expands to code that the user could have written. // // For the nodes that are related to the bind-attribute rewrite them to look like a pair of // 'normal' HTML attributes similar to the following transformation. // // Input: <MyComponent bind-Value="@currentCount" /> // Output: <MyComponent Value ="...<get the value>..." ValueChanged ="... <set the value>..." /> // // This means that the expression that appears inside of 'bind' must be an LValue or else // there will be errors. In general the errors that come from C# in this case are good enough // to understand the problem. // // The BindMethods calls are required in this case because to give us a good experience. They // use overloading to ensure that can get an Action<object> that will convert and set an arbitrary // value. // // We also assume that the element will be treated as a component for now because // multiple passes handle 'special' tag helpers. We have another pass that translates // a tag helper node back into 'regular' element when it doesn't have an associated component if (!TryComputeAttributeNames( node, attributeNode.AttributeName, out var valueAttributeName, out var changeAttributeName, out var valueAttribute, out var changeAttribute)) { // Skip anything we can't understand. It's important that we don't crash, that will bring down // the build. node.Diagnostics.Add(BlazorDiagnosticFactory.CreateBindAttribute_InvalidSyntax( attributeNode.Source, attributeNode.AttributeName)); return; } var original = GetAttributeContent(attributeNode); if (string.IsNullOrEmpty(original.Content)) { // This can happen in error cases, the parser will already have flagged this // as an error, so ignore it. return; } // Look for a matching format node. If we find one then we need to pass the format into the // two nodes we generate. IntermediateToken format = null; if (TryGetFormatNode(node, attributeNode, valueAttributeName, out var formatNode)) { // Don't write the format out as its own attribute, just capture it as a string // or expression. node.Children.Remove(formatNode); format = GetAttributeContent(formatNode); } var valueAttributeNode = new ComponentAttributeExtensionNode(attributeNode) { AttributeName = valueAttributeName, BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute PropertyName = valueAttribute?.GetPropertyName(), TagHelper = valueAttribute == null ? null : attributeNode.TagHelper, }; node.Children.Insert(index, valueAttributeNode); // Now rewrite the content of the value node to look like: // // BindMethods.GetValue(<code>) OR // BindMethods.GetValue(<code>, <format>) valueAttributeNode.Children.Clear(); var expression = new CSharpExpressionIntermediateNode(); valueAttributeNode.Children.Add(expression); expression.Children.Add(new IntermediateToken() { Content = $"{BlazorApi.BindMethods.GetValue}(", Kind = TokenKind.CSharp }); expression.Children.Add(original); if (!string.IsNullOrEmpty(format?.Content)) { expression.Children.Add(new IntermediateToken() { Content = ", ", Kind = TokenKind.CSharp, }); expression.Children.Add(format); } expression.Children.Add(new IntermediateToken() { Content = ")", Kind = TokenKind.CSharp, }); var changeAttributeNode = new ComponentAttributeExtensionNode(attributeNode) { AttributeName = changeAttributeName, BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute PropertyName = changeAttribute?.GetPropertyName(), TagHelper = changeAttribute == null ? null : attributeNode.TagHelper, }; node.Children[index + 1] = changeAttributeNode; // Now rewrite the content of the change-handler node. There are two cases we care about // here. If it's a component attribute, then don't use the 'BindMethods wrapper. We expect // component attributes to always 'match' on type. // // __value => <code> = __value // // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs // so we use BindMethods.SetValueHandler // // BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR // BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>) // // Note that the linemappings here are applied to the value attribute, not the change attribute. string changeAttributeContent = null; if (changeAttributeNode.BoundAttribute == null && format == null) { changeAttributeContent = $"{BlazorApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})"; } else if (changeAttributeNode.BoundAttribute == null && format != null) { changeAttributeContent = $"{BlazorApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})"; } else { changeAttributeContent = $"__value => {original.Content} = __value"; } changeAttributeNode.Children.Clear(); changeAttributeNode.Children.Add(new CSharpExpressionIntermediateNode() { Children = { new IntermediateToken() { Content = changeAttributeContent, Kind = TokenKind.CSharp }, }, }); }
public override void VisitCSharpExpression(CSharpExpressionIntermediateNode node) { Context.NodeWriter.WriteCSharpExpression(Context, node); }
public abstract void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node);
public virtual void VisitCSharpExpression(CSharpExpressionIntermediateNode node) { VisitDefault(node); }