Example #1
0
            public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
            {
                if (node.HasDiagnostics)
                {
                    // Treat node with errors as non-HTML
                    _foundNonHtml = true;
                }

                // Visit Children
                base.VisitDefault(node);
            }
Example #2
0
            public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
            {
                Builder.Append(" ");
                Builder.Append(node.AttributeName);

                if (node.Children.Count == 0)
                {
                    // Minimized attribute
                    return;
                }

                Builder.Append("=\"");

                // Visit Children
                base.VisitDefault(node);

                Builder.Append("\"");
            }
        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]);
            }
        }
Example #4
0
            public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
            {
                Builder.Append(' ');
                Builder.Append(node.AttributeName);

                if (node.Children.Count == 0)
                {
                    // Minimized attribute
                    return;
                }

                // We examine the node.Prefix (e.g. " onfocus='" or " on focus=\"")
                // to preserve the quote type that is used in the original markup.
                var quoteType = node.Prefix.EndsWith("'", StringComparison.Ordinal) ? "'" : "\"";

                Builder.Append('=');
                Builder.Append(quoteType);

                // Visit Children
                base.VisitDefault(node);

                Builder.Append(quoteType);
            }
Example #5
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(ComponentDiagnosticFactory.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 = $"{ComponentsApi.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 = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
            }
            else if (changeAttribute == null && format != null)
            {
                changeExpressionContent = $"{ComponentsApi.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 MarkupElementIntermediateNode)
            {
                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 ComponentAttributeIntermediateNode(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,
                    TypeName       = valueAttribute?.IsWeaklyTyped() == false ? valueAttribute.TypeName : null,
                };

                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 ComponentAttributeIntermediateNode(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,
                    TypeName       = changeAttribute?.IsWeaklyTyped() == false ? changeAttribute.TypeName : null,
                };

                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 });
            }
        }
Example #6
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);
            }
        }
 public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
 {
     // Don't rewrite inside of attributes
 }
 public virtual void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
 {
     VisitDefault(node);
 }
Example #9
0
 public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
 {
     WriteContentNode(node, node.Prefix, node.Suffix);
 }
Example #10
0
            public override void VisitTagHelperHtmlAttribute(TagHelperHtmlAttributeIntermediateNode node)
            {
                var attribute = new HtmlAttributeIntermediateNode()
                {
                    AttributeName = node.AttributeName,
                    Source        = node.Source,
                };

                _children.Add(attribute);

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

                switch (node.AttributeStructure)
                {
                case AttributeStructure.Minimized:

                    attribute.Prefix = node.AttributeName;
                    attribute.Suffix = string.Empty;
                    break;

                case AttributeStructure.NoQuotes:
                case AttributeStructure.SingleQuotes:
                case AttributeStructure.DoubleQuotes:

                    // We're ignoring attribute structure here for simplicity, it doesn't effect us.
                    attribute.Prefix = node.AttributeName + "=\"";
                    attribute.Suffix = "\"";

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

                    break;
                }

                IntermediateNode RewriteAttributeContent(IntermediateNode content)
                {
                    if (content is HtmlContentIntermediateNode html)
                    {
                        var value = new HtmlAttributeValueIntermediateNode()
                        {
                            Source = content.Source,
                        };

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

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

                        return(value);
                    }


                    return(content);
                }
            }
 public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
 {
     Context.NodeWriter.WriteHtmlAttribute(Context, node);
 }
Example #12
0
        private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperDirectiveAttributeIntermediateNode 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:
            //
            // EventCallback.Factory.Create<T>(this, <code>)
            //
            // This method is overloaded on string and T, 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>(original.Count + 2)
            {
                new IntermediateToken()
                {
                    Content = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateMethod}<{eventArgsType}>(this, ",
                    Kind    = TokenKind.CSharp
                },
                new IntermediateToken()
                {
                    Content = $")",
                    Kind    = TokenKind.CSharp
                }
            };

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

            var attributeName = node.AttributeName;

            if (parent is MarkupElementIntermediateNode)
            {
                var result = new HtmlAttributeIntermediateNode()
                {
                    Annotations =
                    {
                        [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
                    },
                    AttributeName = attributeName,
                    Source        = node.Source,

                    Prefix = 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 ComponentAttributeIntermediateNode(node)
                {
                    Annotations =
                    {
                        [ComponentMetadata.Common.OriginalAttributeName] = node.OriginalAttributeName,
                    },
                };

                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);
            }
        }
Example #13
0
 public override void VisitHtmlAttribute(HtmlAttributeIntermediateNode node)
 {
     _nextAttributeName = node.AttributeName;
     RenderChildren(node);
 }
 public abstract void WriteHtmlAttribute(CodeRenderingContext context, HtmlAttributeIntermediateNode node);
Example #15
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 set of
            // 'normal' HTML attributes similar to the following transformation.
            //
            // Input:   <MyComponent bind-Value="@currentCount" />
            // Output:  <MyComponent Value ="...<get the value>..." ValueChanged ="... <set the value>..." ValueExpression ="() => ...<get 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.
            //
            // We also support and encourage the use of EventCallback<> with bind. So in the above example
            // the ValueChanged property could be an Action<> or an EventCallback<>.
            //
            // The BindMethods calls are required with Action<> 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 have a similar set of APIs to use with EventCallback<>.
            //
            // 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 expressionAttributeName,
                    out var valueAttribute,
                    out var changeAttribute,
                    out var expressionAttribute))
            {
                // Skip anything we can't understand. It's important that we don't crash, that will bring down
                // the build.
                node.Diagnostics.Add(ComponentDiagnosticFactory.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);
            }

            var valueExpressionTokens  = new List <IntermediateToken>();
            var changeExpressionTokens = new List <IntermediateToken>();

            if (changeAttribute != null && changeAttribute.IsDelegateProperty())
            {
                RewriteNodesForDelegateBind(
                    original,
                    format,
                    valueAttribute,
                    changeAttribute,
                    valueExpressionTokens,
                    changeExpressionTokens);
            }
            else
            {
                RewriteNodesForEventCallbackBind(
                    original,
                    format,
                    valueAttribute,
                    changeAttribute,
                    valueExpressionTokens,
                    changeExpressionTokens);
            }

            if (parent is MarkupElementIntermediateNode)
            {
                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 ComponentAttributeIntermediateNode(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,
                    TypeName       = valueAttribute?.IsWeaklyTyped() == false ? valueAttribute.TypeName : null,
                };

                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 ComponentAttributeIntermediateNode(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,
                    TypeName       = changeAttribute?.IsWeaklyTyped() == false ? changeAttribute.TypeName : null,
                };

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

                // Finally, also emit a node for the "Expression" attribute, but only if the target
                // component is defined to accept one
                ComponentAttributeIntermediateNode expressionNode = null;
                if (expressionAttribute != null)
                {
                    expressionNode = new ComponentAttributeIntermediateNode(node)
                    {
                        AttributeName  = expressionAttributeName,
                        BoundAttribute = expressionAttribute,
                        PropertyName   = expressionAttribute.GetPropertyName(),
                        TagHelper      = node.TagHelper,
                        TypeName       = expressionAttribute.IsWeaklyTyped() ? null : expressionAttribute.TypeName,
                    };

                    expressionNode.Children.Clear();
                    expressionNode.Children.Add(new CSharpExpressionIntermediateNode());
                    expressionNode.Children[0].Children.Add(new IntermediateToken()
                    {
                        Content = $"() => {original.Content}",
                        Kind    = TokenKind.CSharp
                    });
                }

                return(expressionNode == null
                    ? new[] { valueNode, changeNode }
                    : new[] { valueNode, changeNode, expressionNode });
            }
        }