public ReferenceCaptureIntermediateNode(IntermediateToken identifierToken, string componentCaptureTypeName)
        : this(identifierToken)
    {
        if (string.IsNullOrEmpty(componentCaptureTypeName))
        {
            throw new ArgumentException("Cannot be null or empty", nameof(componentCaptureTypeName));
        }

        IsComponentCapture       = true;
        ComponentCaptureTypeName = componentCaptureTypeName;
    }
Example #2
0
            public override void VisitToken(IntermediateToken node)
            {
                if (node.HasDiagnostics)
                {
                    // Treat node with errors as non-HTML
                    _foundNonHtml = true;
                }

                if (node.IsCSharp)
                {
                    _foundNonHtml = true;
                }
            }
 private IntermediateNode CreateField(string fieldType, IntermediateToken identifier)
 {
     return(new FieldDeclarationIntermediateNode()
     {
         FieldName = identifier.Content,
         FieldType = fieldType,
         Modifiers = { "private" },
         SuppressWarnings =
         {
             "0414", // Field is assigned by never used
             "0169", // Field is never used
         },
     });
 }
        private IntermediateToken DetermineIdentifierToken(TagHelperDirectiveAttributeIntermediateNode 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);
        }
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 });
            }
        }
 public virtual void VisitToken(IntermediateToken node)
 {
     VisitDefault(node);
 }
Example #7
0
 public override void VisitToken(IntermediateToken node)
 {
     Builder.Append(node.Content);
 }
Example #8
0
 public override void VisitToken(IntermediateToken node)
 {
     WriteContentNode(node, node.Kind.ToString(), node.Content);
 }
 public ReferenceCaptureIntermediateNode(IntermediateToken identifierToken)
 {
     IdentifierToken = identifierToken ?? throw new ArgumentNullException(nameof(identifierToken));
     Source          = IdentifierToken.Source;
 }
        private ReferenceCaptureIntermediateNode RewriteUsage(IntermediateNode parent, IntermediateToken identifier)
        {
            // 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 = (parent as ComponentIntermediateNode)?.Component;

            if (componentTagHelper != null)
            {
                return(new ReferenceCaptureIntermediateNode(identifier, componentTagHelper.GetTypeName()));
            }
            else
            {
                return(new ReferenceCaptureIntermediateNode(identifier));
            }
        }
 public SetKeyIntermediateNode(IntermediateToken keyValueToken)
 {
     KeyValueToken = keyValueToken ?? throw new ArgumentNullException(nameof(keyValueToken));
     Source        = KeyValueToken.Source;
 }
Example #12
0
 public RefExtensionNode(IntermediateToken identifierToken)
 {
     IdentifierToken = identifierToken ?? throw new ArgumentNullException(nameof(identifierToken));
     Source          = IdentifierToken.Source;
 }
Example #13
0
        private void RewriteNodesForEventCallbackBind(
            IntermediateToken original,
            IntermediateToken format,
            BoundAttributeDescriptor valueAttribute,
            BoundAttributeDescriptor changeAttribute,
            List <IntermediateToken> valueExpressionTokens,
            List <IntermediateToken> changeExpressionTokens)
        {
            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetValue(<code>) OR
            // BindMethods.GetValue(<code>, <format>)
            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 'CreateBinder' wrapper. We expect
            // component attributes to always 'match' on type.
            //
            // The really tricky part of this is that we CANNOT write the type name of of the EventCallback we
            // intend to create. Doing so would really complicate the story for how we deal with generic types,
            // since the generic type lowering pass runs after this. To keep this simple we're relying on
            // the compiler to resolve overloads for us.
            //
            // EventCallbackFactory.CreateInferred(this, __value => <code> = __value, <code>)
            //
            // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
            // so we use 'CreateBinder'
            //
            // EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>) OR
            // EventCallbackFactory.CreateBinder(this, __value => <code> = __value, <code>, <format>)
            //
            // Note that the linemappings here are applied to the value attribute, not the change attribute.

            string changeExpressionContent;

            if (changeAttribute == null && format == null)
            {
                // DOM
                changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content})";
            }
            else if (changeAttribute == null && format != null)
            {
                // DOM + format
                changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateBinderMethod}(this, __value => {original.Content} = __value, {original.Content}, {format.Content})";
            }
            else
            {
                // Component
                changeExpressionContent = $"{ComponentsApi.EventCallback.FactoryAccessor}.{ComponentsApi.EventCallbackFactory.CreateInferredMethod}(this, __value => {original.Content} = __value, {original.Content})";
            }
            changeExpressionTokens.Add(new IntermediateToken()
            {
                Content = changeExpressionContent,
                Kind    = TokenKind.CSharp
            });
        }
Example #14
0
        private void RewriteNodesForDelegateBind(
            IntermediateToken original,
            IntermediateToken format,
            BoundAttributeDescriptor valueAttribute,
            BoundAttributeDescriptor changeAttribute,
            List <IntermediateToken> valueExpressionTokens,
            List <IntermediateToken> changeExpressionTokens)
        {
            // Now rewrite the content of the value node to look like:
            //
            // BindMethods.GetValue(<code>) OR
            // BindMethods.GetValue(<code>, <format>)
            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;

            if (changeAttribute == null && format == null)
            {
                // DOM
                changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content})";
            }
            else if (changeAttribute == null && format != null)
            {
                // DOM + format
                changeExpressionContent = $"{ComponentsApi.BindMethods.SetValueHandler}(__value => {original.Content} = __value, {original.Content}, {format.Content})";
            }
            else
            {
                // Component
                changeExpressionContent = $"__value => {original.Content} = __value";
            }
            changeExpressionTokens.Add(new IntermediateToken()
            {
                Content = changeExpressionContent,
                Kind    = TokenKind.CSharp
            });
        }
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 });
            }
        }