示例#1
0
    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);
        }
示例#4
0
 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);
     }
 }
示例#5
0
            public override void VisitCSharpExpression(CSharpExpressionIntermediateNode node)
            {
                if (node.Source != null)
                {
                    Items.Add(new InstrumentationItem(node, Parent, isLiteral: false, source: node.Source.Value));
                }

                VisitDefault(node);
            }
示例#6
0
            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);
                }
示例#7
0
            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;
                    }
                }
            }
示例#8
0
        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);
        }
示例#9
0
            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);
            }
示例#10
0
        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);
        }
示例#11
0
        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]);
                        }
                    }
示例#12
0
        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);
        }
示例#13
0
        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]);
                        }
                    }
示例#14
0
        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);
                    }
                }
            }
示例#16
0
            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);
                    }
                }
            }
示例#17
0
        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
                    },
                },
            });
        }
示例#18
0
 public override void VisitCSharpExpression(CSharpExpressionIntermediateNode node)
 {
     Context.NodeWriter.WriteCSharpExpression(Context, node);
 }
示例#19
0
 public abstract void WriteCSharpExpression(CodeRenderingContext context, CSharpExpressionIntermediateNode node);
 public virtual void VisitCSharpExpression(CSharpExpressionIntermediateNode node)
 {
     VisitDefault(node);
 }