Esempio n. 1
0
        public ComponentAttributeExtensionNode(ComponentAttributeExtensionNode attributeNode)
        {
            if (attributeNode == null)
            {
                throw new ArgumentNullException(nameof(attributeNode));
            }

            AttributeName      = attributeNode.AttributeName;
            AttributeStructure = attributeNode.AttributeStructure;
            BoundAttribute     = attributeNode.BoundAttribute;
            PropertyName       = attributeNode.BoundAttribute.GetPropertyName();
            Source             = attributeNode.Source;
            TagHelper          = attributeNode.TagHelper;
            TypeName           = attributeNode.BoundAttribute.IsWeaklyTyped() ? null : attributeNode.BoundAttribute.TypeName;

            for (var i = 0; i < attributeNode.Children.Count; i++)
            {
                Children.Add(attributeNode.Children[i]);
            }

            for (var i = 0; i < attributeNode.Diagnostics.Count; i++)
            {
                Diagnostics.Add(attributeNode.Diagnostics[i]);
            }
        }
Esempio n. 2
0
            public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
            {
                var attribute = new ComponentAttributeExtensionNode(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)
                    {
                        attribute.Children[i] = new HtmlContentIntermediateNode()
                        {
                            Children =
                            {
                                htmlValue.Children.Single(),
                            },
                            Source = htmlValue.Source,
                        };
                    }
                    else if (attribute.Children[i] is CSharpExpressionAttributeValueIntermediateNode expressionValue)
                    {
                        attribute.Children[i] = new CSharpExpressionIntermediateNode()
                        {
                            Children =
                            {
                                expressionValue.Children.Single(),
                            },
                            Source = expressionValue.Source,
                        };
                    }
                    else if (attribute.Children[i] is CSharpCodeAttributeValueIntermediateNode codeValue)
                    {
                        attribute.Children[i] = new CSharpExpressionIntermediateNode()
                        {
                            Children =
                            {
                                codeValue.Children.Single(),
                            },
                            Source = codeValue.Source,
                        };
                    }
                }
            }
Esempio n. 3
0
        private void RewriteUsage(TagHelperIntermediateNode node, int index, ComponentAttributeExtensionNode attributeNode)
        {
            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;
            }

            var rewrittenNode = new ComponentAttributeExtensionNode(attributeNode);

            node.Children[index] = rewrittenNode;

            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetEventHandlerValue<TDelegate>(<code>)
            //
            // This method is overloaded on string and TDelegate, which means that it will put the code in the
            // correct context for intellisense when typing in the attribute.
            var eventArgsType = attributeNode.TagHelper.GetEventArgsType();

            rewrittenNode.Children.Clear();
            rewrittenNode.Children.Add(new CSharpExpressionIntermediateNode()
            {
                Children =
                {
                    new IntermediateToken()
                    {
                        Content = $"{BlazorApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(",
                        Kind    = TokenKind.CSharp
                    },
                    original,
                    new IntermediateToken()
                    {
                        Content = $")",
                        Kind    = TokenKind.CSharp
                    }
                },
            });
        }
Esempio n. 4
0
 private static string GetAttributeContent(ComponentAttributeExtensionNode node)
 {
     if (node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
     {
         // This case can be hit for a 'string' attribute. We want to turn it into
         // an expression.
         return("\"" + ((IntermediateToken)htmlContentNode.Children.Single()).Content + "\"");
     }
     else if (node.Children[0] is CSharpExpressionIntermediateNode cSharpNode)
     {
         // This case can be hit when the attribute has an explicit @ inside, which
         // 'escapes' any special sugar we provide for codegen.
         return(((IntermediateToken)cSharpNode.Children.Single()).Content);
     }
     else
     {
         // This is the common case for 'mixed' content
         return(((IntermediateToken)node.Children.Single()).Content);
     }
 }
        private void RewriteComponentAttributeContent(List <IntermediateNode> nodes, ComponentAttributeExtensionNode node)
        {
            var attributeNode = new HtmlAttributeIntermediateNode()
            {
                AttributeName = node.AttributeName,
                Prefix        = "=\"",
                Suffix        = "\"",
            };

            nodes.Add(attributeNode);

            var valueNode = new CSharpExpressionAttributeValueIntermediateNode();

            attributeNode.Children.Add(valueNode);

            for (var i = 0; i < node.Children[0].Children.Count; i++)
            {
                valueNode.Children.Add(node.Children[0].Children[i]);
            }
        }
Esempio n. 6
0
        private IntermediateToken DetermineIdentifierToken(ComponentAttributeExtensionNode attributeNode)
        {
            IntermediateToken foundToken = null;

            if (attributeNode.Children.Count == 1)
            {
                if (attributeNode.Children[0] is IntermediateToken token)
                {
                    foundToken = token;
                }
                else if (attributeNode.Children[0] is CSharpExpressionIntermediateNode csharpNode)
                {
                    if (csharpNode.Children.Count == 1)
                    {
                        foundToken = csharpNode.Children[0] as IntermediateToken;
                    }
                }
            }

            return(!string.IsNullOrWhiteSpace(foundToken?.Content) ? foundToken : null);
        }
Esempio n. 7
0
        private bool TryGetFormatNode(
            TagHelperIntermediateNode node,
            ComponentAttributeExtensionNode attributeNode,
            string valueAttributeName,
            out ComponentAttributeExtensionNode formatNode)
        {
            for (var i = 0; i < node.Children.Count; i++)
            {
                var child = node.Children[i] as ComponentAttributeExtensionNode;
                if (child != null &&
                    child.TagHelper != null &&
                    child.TagHelper == attributeNode.TagHelper &&
                    child.AttributeName == "format-" + valueAttributeName)
                {
                    formatNode = child;
                    return(true);
                }
            }

            formatNode = null;
            return(false);
        }
Esempio n. 8
0
 public abstract void WriteComponentAttribute(CodeRenderingContext context, ComponentAttributeExtensionNode node);
Esempio n. 9
0
        private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node)
        {
            var original = GetAttributeContent(node);

            if (original.Count == 0)
            {
                // This can happen in error cases, the parser will already have flagged this
                // as an error, so ignore it.
                return(node);
            }

            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetEventHandlerValue<TDelegate>(<code>)
            //
            // This method is overloaded on string and TDelegate, which means that it will put the code in the
            // correct context for intellisense when typing in the attribute.
            var eventArgsType = node.TagHelper.GetEventArgsType();
            var tokens        = new List <IntermediateToken>()
            {
                new IntermediateToken()
                {
                    Content = $"{BlazorApi.BindMethods.GetEventHandlerValue}<{eventArgsType}>(",
                    Kind    = TokenKind.CSharp
                },
                new IntermediateToken()
                {
                    Content = $")",
                    Kind    = TokenKind.CSharp
                }
            };

            for (var i = 0; i < original.Count; i++)
            {
                tokens.Insert(i + 1, original[i]);
            }

            if (parent is HtmlElementIntermediateNode)
            {
                var result = new HtmlAttributeIntermediateNode()
                {
                    AttributeName = node.AttributeName,
                    Source        = node.Source,

                    Prefix = node.AttributeName + "=\"",
                    Suffix = "\"",
                };

                for (var i = 0; i < node.Diagnostics.Count; i++)
                {
                    result.Diagnostics.Add(node.Diagnostics[i]);
                }

                result.Children.Add(new CSharpExpressionAttributeValueIntermediateNode());
                for (var i = 0; i < tokens.Count; i++)
                {
                    result.Children[0].Children.Add(tokens[i]);
                }

                return(result);
            }
            else
            {
                var result = new ComponentAttributeExtensionNode(node);

                result.Children.Clear();
                result.Children.Add(new CSharpExpressionIntermediateNode());
                for (var i = 0; i < tokens.Count; i++)
                {
                    result.Children[0].Children.Add(tokens[i]);
                }

                return(result);
            }
        }
Esempio n. 10
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.
                return;
            }

            var originalContent = GetAttributeContent(attributeNode);

            if (string.IsNullOrEmpty(originalContent))
            {
                // 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.
            string format = null;

            if (TryGetFormatNode(node,
                                 attributeNode,
                                 valueAttributeName,
                                 out var formatNode))
            {
                // Don't write the format out as its own attribute;
                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>)
            //
            // For now, the way this is done isn't debuggable. But since the expression
            // passed here must be an LValue, it's probably not important.
            var valueNodeContent = format == null ?
                                   $"{BlazorApi.BindMethods.GetValue}({originalContent})" :
                                   $"{BlazorApi.BindMethods.GetValue}({originalContent}, {format})";

            valueAttributeNode.Children.Clear();
            valueAttributeNode.Children.Add(new CSharpExpressionIntermediateNode()
            {
                Children =
                {
                    new IntermediateToken()
                    {
                        Content = valueNodeContent,
                        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>)
            //
            // For now, the way this is done isn't debuggable. But since the expression
            // passed here must be an LValue, it's probably not important.
            string changeAttributeContent = null;

            if (changeAttributeNode.BoundAttribute == null && format == null)
            {
                changeAttributeContent = $"{BlazorApi.BindMethods.SetValueHandler}(__value => {originalContent} = __value, {originalContent})";
            }
            else if (changeAttributeNode.BoundAttribute == null && format != null)
            {
                changeAttributeContent = $"{BlazorApi.BindMethods.SetValueHandler}(__value => {originalContent} = __value, {originalContent}, {format})";
            }
            else
            {
                changeAttributeContent = $"__value => {originalContent} = __value";
            }

            changeAttributeNode.Children.Clear();
            changeAttributeNode.Children.Add(new CSharpExpressionIntermediateNode()
            {
                Children =
                {
                    new IntermediateToken()
                    {
                        Content = changeAttributeContent,
                        Kind    = TokenKind.CSharp
                    },
                },
            });
        }
Esempio n. 11
0
        private IntermediateNode[] RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node)
        {
            // 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(
                    parent,
                    node,
                    node.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(
                                         node.Source,
                                         node.AttributeName));
                return(new[] { node });
            }

            var original = GetAttributeContent(node);

            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(new[] { node });
            }

            // 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(
                    parent,
                    node,
                    valueAttributeName,
                    out var formatNode))
            {
                // Don't write the format out as its own attribute, just capture it as a string
                // or expression.
                parent.Children.Remove(formatNode);
                format = GetAttributeContent(formatNode);
            }

            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetValue(<code>) OR
            // BindMethods.GetValue(<code>, <format>)
            var valueExpressionTokens = new List <IntermediateToken>();

            valueExpressionTokens.Add(new IntermediateToken()
            {
                Content = $"{BlazorApi.BindMethods.GetValue}(",
                Kind    = TokenKind.CSharp
            });
            valueExpressionTokens.Add(original);
            if (!string.IsNullOrEmpty(format?.Content))
            {
                valueExpressionTokens.Add(new IntermediateToken()
                {
                    Content = ", ",
                    Kind    = TokenKind.CSharp,
                });
                valueExpressionTokens.Add(format);
            }
            valueExpressionTokens.Add(new IntermediateToken()
            {
                Content = ")",
                Kind    = TokenKind.CSharp,
            });

            // 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 changeExpressionContent = null;

            if (changeAttribute == null && format == null)
            {
                changeExpressionContent = $"{BlazorApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
            }
            else if (changeAttribute == null && format != null)
            {
                changeExpressionContent = $"{BlazorApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
            }
            else
            {
                changeExpressionContent = $"__value => {original.Content} = __value";
            }
            var changeExpressionTokens = new List <IntermediateToken>()
            {
                new IntermediateToken()
                {
                    Content = changeExpressionContent,
                    Kind    = TokenKind.CSharp
                }
            };

            if (parent is HtmlElementIntermediateNode)
            {
                var valueNode = new HtmlAttributeIntermediateNode()
                {
                    AttributeName = valueAttributeName,
                    Source        = node.Source,

                    Prefix = valueAttributeName + "=\"",
                    Suffix = "\"",
                };

                for (var i = 0; i < node.Diagnostics.Count; i++)
                {
                    valueNode.Diagnostics.Add(node.Diagnostics[i]);
                }

                valueNode.Children.Add(new CSharpExpressionAttributeValueIntermediateNode());
                for (var i = 0; i < valueExpressionTokens.Count; i++)
                {
                    valueNode.Children[0].Children.Add(valueExpressionTokens[i]);
                }

                var changeNode = new HtmlAttributeIntermediateNode()
                {
                    AttributeName = changeAttributeName,
                    Source        = node.Source,

                    Prefix = changeAttributeName + "=\"",
                    Suffix = "\"",
                };

                changeNode.Children.Add(new CSharpExpressionAttributeValueIntermediateNode());
                for (var i = 0; i < changeExpressionTokens.Count; i++)
                {
                    changeNode.Children[0].Children.Add(changeExpressionTokens[i]);
                }

                return(new[] { valueNode, changeNode });
            }
            else
            {
                var valueNode = new ComponentAttributeExtensionNode(node)
                {
                    AttributeName  = valueAttributeName,
                    BoundAttribute = valueAttribute, // Might be null if it doesn't match a component attribute
                    PropertyName   = valueAttribute?.GetPropertyName(),
                    TagHelper      = valueAttribute == null ? null : node.TagHelper,
                };

                valueNode.Children.Clear();
                valueNode.Children.Add(new CSharpExpressionIntermediateNode());
                for (var i = 0; i < valueExpressionTokens.Count; i++)
                {
                    valueNode.Children[0].Children.Add(valueExpressionTokens[i]);
                }

                var changeNode = new ComponentAttributeExtensionNode(node)
                {
                    AttributeName  = changeAttributeName,
                    BoundAttribute = changeAttribute, // Might be null if it doesn't match a component attribute
                    PropertyName   = changeAttribute?.GetPropertyName(),
                    TagHelper      = changeAttribute == null ? null : node.TagHelper,
                };

                changeNode.Children.Clear();
                changeNode.Children.Add(new CSharpExpressionIntermediateNode());
                for (var i = 0; i < changeExpressionTokens.Count; i++)
                {
                    changeNode.Children[0].Children.Add(changeExpressionTokens[i]);
                }

                return(new[] { valueNode, changeNode });
            }
        }
Esempio n. 12
0
        private void RewriteUsage(ClassDeclarationIntermediateNode classNode, TagHelperIntermediateNode node, int index, ComponentAttributeExtensionNode attributeNode)
        {
            // If we can't get a nonempty attribute name, do nothing because there will
            // already be a diagnostic for empty values
            var identifierToken = DetermineIdentifierToken(attributeNode);

            if (identifierToken != null)
            {
                node.Children.Remove(attributeNode);

                // Determine whether this is an element capture or a component capture, and
                // if applicable the type name that will appear in the resulting capture code
                var componentTagHelper = node.TagHelpers.FirstOrDefault(x => x.IsComponentTagHelper());
                if (componentTagHelper != null)
                {
                    // For components, the RefExtensionNode must go after all ComponentAttributeExtensionNode
                    // and ComponentBodyExtensionNode siblings because they translate to AddAttribute calls.
                    // We can therefore put it immediately before the ComponentCloseExtensionNode.
                    var componentCloseNodePosition = LastIndexOf(node.Children, n => n is ComponentCloseExtensionNode);
                    if (componentCloseNodePosition < 0)
                    {
                        // Should never happen - would imply we're running the lowering passes in the wrong order
                        throw new InvalidOperationException($"Cannot find {nameof(ComponentCloseExtensionNode)} among ref node siblings.");
                    }

                    var refExtensionNode = new RefExtensionNode(identifierToken, componentTagHelper.GetTypeName());
                    node.Children.Insert(componentCloseNodePosition, refExtensionNode);
                }
                else
                {
                    // For elements, it doesn't matter how the RefExtensionNode is positioned
                    // among the children, as the node writer takes care of emitting the
                    // code at the right point after the AddAttribute calls
                    node.Children.Add(new RefExtensionNode(identifierToken));
                }
            }
        }