示例#1
0
 public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
 {
     // Each 'tag helper property' belongs to a specific tag helper. We want to handle
     // the cases for components, but leave others alone. This allows our other passes
     // to handle those cases.
     _children.Add(node.TagHelper.IsComponentTagHelper() ? (IntermediateNode) new ComponentAttributeIntermediateNode(node) : node);
 }
示例#2
0
        private static IReadOnlyList <IntermediateToken> GetAttributeContent(TagHelperPropertyIntermediateNode node)
        {
            var template = node.FindDescendantNodes <TemplateIntermediateNode>().FirstOrDefault();

            if (template != null)
            {
                // See comments in TemplateDiagnosticPass
                node.Diagnostics.Add(BlazorDiagnosticFactory.CreateTemplate_InvalidLocation(template.Source));
                return(new[] { new IntermediateToken()
                               {
                                   Kind = TokenKind.CSharp, Content = string.Empty,
                               }, });
            }

            if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
            {
                // This case can be hit for a 'string' attribute. We want to turn it into
                // an expression.
                var tokens = htmlContentNode.FindDescendantNodes <IntermediateToken>();

                var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content)) + "\"";
                return(new[] { new IntermediateToken()
                               {
                                   Content = content, Kind = TokenKind.CSharp,
                               } });
            }
            else
            {
                return(node.FindDescendantNodes <IntermediateToken>());
            }
        }
示例#3
0
        public ComponentAttributeExtensionNode(TagHelperPropertyIntermediateNode propertyNode)
        {
            if (propertyNode == null)
            {
                throw new ArgumentNullException(nameof(propertyNode));
            }

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

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

            for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
            {
                Diagnostics.Add(propertyNode.Diagnostics[i]);
            }
        }
示例#4
0
        private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode 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.
                var content = "\"" + string.Join(string.Empty, htmlContentNode.Children.OfType <IntermediateToken>().Select(t => t.Content)) + "\"";
                return(new IntermediateToken()
                {
                    Kind = TokenKind.CSharp, Content = 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(GetToken(cSharpNode));
            }
            else
            {
                // This is the common case for 'mixed' content
                return(GetToken(node));
            }

            // In error cases we won't have a single token, but we still want to generate the code.
            IntermediateToken GetToken(IntermediateNode parent)
            {
                return
                    (parent.Children.Count == 1 ? (IntermediateToken)parent.Children[0] : new IntermediateToken()
                {
                    Kind = TokenKind.CSharp,
                    Content = string.Join(string.Empty, parent.Children.OfType <IntermediateToken>().Select(t => t.Content)),
                });
            }
        }
            private void AddTagHelperAttributes(IList <TagHelperAttributeNode> attributes, TagHelperBinding tagHelperBinding)
            {
                var descriptors = tagHelperBinding.Descriptors;
                var renderedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

                foreach (var attribute in attributes)
                {
                    var attributeValueNode    = attribute.Value;
                    var associatedDescriptors = descriptors.Where(descriptor =>
                                                                  descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, attributeDescriptor)));

                    if (associatedDescriptors.Any() && renderedBoundAttributeNames.Add(attribute.Name))
                    {
                        if (attributeValueNode == null)
                        {
                            // Minimized attributes are not valid for bound attributes. TagHelperBlockRewriter has already
                            // logged an error if it was a bound attribute; so we can skip.
                            continue;
                        }

                        foreach (var associatedDescriptor in associatedDescriptors)
                        {
                            var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
                            {
                                return(TagHelperMatchingConventions.CanSatisfyBoundAttribute(attribute.Name, a));
                            });

                            var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
                            {
                                AttributeName      = attribute.Name,
                                BoundAttribute     = associatedAttributeDescriptor,
                                TagHelper          = associatedDescriptor,
                                AttributeStructure = attribute.AttributeStructure,
                                Source             = BuildSourceSpanFromNode(attributeValueNode),
                                IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attribute.Name, associatedAttributeDescriptor),
                            };

                            _builder.Push(setTagHelperProperty);
                            attributeValueNode.Accept(this);
                            _builder.Pop();
                        }
                    }
                    else
                    {
                        var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
                        {
                            AttributeName      = attribute.Name,
                            AttributeStructure = attribute.AttributeStructure
                        };

                        _builder.Push(addHtmlAttribute);
                        if (attributeValueNode != null)
                        {
                            attributeValueNode.Accept(this);
                        }
                        _builder.Pop();
                    }
                }
            }
示例#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 VisitMarkupMinimizedTagHelperAttribute(MarkupMinimizedTagHelperAttributeSyntax node)
            {
                if (!_featureFlags.AllowMinimizedBooleanTagHelperAttributes)
                {
                    // Minimized attributes are not valid for non-boolean bound attributes. TagHelperBlockRewriter
                    // has already logged an error if it was a non-boolean bound attribute; so we can skip.
                    return;
                }

                var element               = node.FirstAncestorOrSelf <MarkupTagHelperElementSyntax>();
                var descriptors           = element.TagHelperInfo.BindingResult.Descriptors;
                var attributeName         = node.Name.GetContent();
                var associatedDescriptors = descriptors.Where(descriptor =>
                                                              descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));

                if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
                {
                    foreach (var associatedDescriptor in associatedDescriptors)
                    {
                        var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
                        {
                            return(TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a));
                        });

                        var expectsBooleanValue = associatedAttributeDescriptor.ExpectsBooleanValue(attributeName);

                        if (!expectsBooleanValue)
                        {
                            // We do not allow minimized non-boolean bound attributes.
                            return;
                        }

                        var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
                        {
                            AttributeName      = attributeName,
                            BoundAttribute     = associatedAttributeDescriptor,
                            TagHelper          = associatedDescriptor,
                            AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
                            Source             = null,
                            IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
                        };

                        _builder.Add(setTagHelperProperty);
                    }
                }
                else
                {
                    var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
                    {
                        AttributeName      = attributeName,
                        AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
                    };

                    _builder.Add(addHtmlAttribute);
                }
            }
示例#8
0
            private bool TryGetAttributeStringContent(TagHelperPropertyIntermediateNode property, out string content)
            {
                // The success path looks like - a single HTML Attribute Value node with tokens
                if (property.Children.Count == 1 &&
                    property.Children[0] is HtmlContentIntermediateNode html)
                {
                    content = string.Join(string.Empty, html.Children.OfType <IntermediateToken>().Select(n => n.Content));
                    return(true);
                }

                content = null;
                return(false);
            }
示例#9
0
        private IntermediateNode RewriteUsage(IntermediateNode parent, TagHelperPropertyIntermediateNode node)
        {
            // If we can't get a nonempty attribute value, do nothing because there will
            // already be a diagnostic for empty values
            var keyValueToken = DetermineKeyValueToken(node);

            if (keyValueToken == null)
            {
                return(node);
            }

            return(new SetKeyIntermediateNode(keyValueToken));
        }
示例#10
0
        private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode node)
        {
            var template = node.FindDescendantNodes <TemplateIntermediateNode>().FirstOrDefault();

            if (template != null)
            {
                // See comments in TemplateDiagnosticPass
                node.Diagnostics.Add(ComponentDiagnosticFactory.Create_TemplateInvalidLocation(template.Source));
                return(new IntermediateToken()
                {
                    Kind = TokenKind.CSharp, Content = string.Empty,
                });
            }

            if (node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
            {
                // This case can be hit for a 'string' attribute. We want to turn it into
                // an expression.
                var content = "\"" + string.Join(string.Empty, htmlContentNode.Children.OfType <IntermediateToken>().Select(t => t.Content)) + "\"";
                return(new IntermediateToken()
                {
                    Kind = TokenKind.CSharp, Content = 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(GetToken(cSharpNode));
            }
            else
            {
                // This is the common case for 'mixed' content
                return(GetToken(node));
            }

            IntermediateToken GetToken(IntermediateNode parent)
            {
                if (parent.Children.Count == 1 && parent.Children[0] is IntermediateToken token)
                {
                    return(token);
                }

                // In error cases we won't have a single token, but we still want to generate the code.
                return(new IntermediateToken()
                {
                    Kind = TokenKind.CSharp,
                    Content = string.Join(string.Empty, parent.Children.OfType <IntermediateToken>().Select(t => t.Content)),
                });
            }
        }
示例#11
0
            public override void VisitMarkupTagHelperAttribute(MarkupTagHelperAttributeSyntax node)
            {
                var element               = node.FirstAncestorOrSelf <MarkupTagHelperElementSyntax>();
                var descriptors           = element.TagHelperInfo.BindingResult.Descriptors;
                var attributeName         = node.Name.GetContent();
                var attributeValueNode    = node.Value;
                var associatedDescriptors = descriptors.Where(descriptor =>
                                                              descriptor.BoundAttributes.Any(attributeDescriptor => TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, attributeDescriptor)));

                if (associatedDescriptors.Any() && _renderedBoundAttributeNames.Add(attributeName))
                {
                    foreach (var associatedDescriptor in associatedDescriptors)
                    {
                        var associatedAttributeDescriptor = associatedDescriptor.BoundAttributes.First(a =>
                        {
                            return(TagHelperMatchingConventions.CanSatisfyBoundAttribute(attributeName, a));
                        });

                        var setTagHelperProperty = new TagHelperPropertyIntermediateNode()
                        {
                            AttributeName      = attributeName,
                            BoundAttribute     = associatedAttributeDescriptor,
                            TagHelper          = associatedDescriptor,
                            AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure,
                            Source             = BuildSourceSpanFromNode(attributeValueNode),
                            IsIndexerNameMatch = TagHelperMatchingConventions.SatisfiesBoundAttributeIndexer(attributeName, associatedAttributeDescriptor),
                        };

                        _builder.Push(setTagHelperProperty);
                        VisitAttributeValue(attributeValueNode);
                        _builder.Pop();
                    }
                }
                else
                {
                    var addHtmlAttribute = new TagHelperHtmlAttributeIntermediateNode()
                    {
                        AttributeName      = attributeName,
                        AttributeStructure = node.TagHelperAttributeInfo.AttributeStructure
                    };

                    _builder.Push(addHtmlAttribute);
                    VisitAttributeValue(attributeValueNode);
                    _builder.Pop();
                }
            }
示例#12
0
        private static IReadOnlyList <IntermediateToken> GetAttributeContent(TagHelperPropertyIntermediateNode node)
        {
            if (node.Children.Count == 1 && node.Children[0] is HtmlContentIntermediateNode htmlContentNode)
            {
                // This case can be hit for a 'string' attribute. We want to turn it into
                // an expression.
                var tokens = htmlContentNode.FindDescendantNodes <IntermediateToken>();

                var content = "\"" + string.Join(string.Empty, tokens.Select(t => t.Content)) + "\"";
                return(new[] { new IntermediateToken()
                               {
                                   Content = content, Kind = TokenKind.CSharp,
                               } });
            }
            else
            {
                return(node.FindDescendantNodes <IntermediateToken>());
            }
        }
        private IntermediateToken DetermineIdentifierToken(TagHelperPropertyIntermediateNode 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);
        }
    public ComponentTypeArgumentIntermediateNode(TagHelperPropertyIntermediateNode propertyNode)
    {
        if (propertyNode == null)
        {
            throw new ArgumentNullException(nameof(propertyNode));
        }

        BoundAttribute = propertyNode.BoundAttribute;
        Source         = propertyNode.Source;
        TagHelper      = propertyNode.TagHelper;

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

        for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
        {
            Diagnostics.Add(propertyNode.Diagnostics[i]);
        }
    }
示例#15
0
            public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
            {
                // Each 'tag helper property' belongs to a specific tag helper. We want to handle
                // the cases for components, but leave others alone. This allows our other passes
                // to handle those cases.
                if (!node.TagHelper.IsComponentTagHelper())
                {
                    _children.Add(node);
                    return;
                }

                // Another special case here - this might be a type argument. These don't represent 'real' parameters
                // that get passed to the component, it needs special code generation support.
                if (node.TagHelper.IsGenericTypedComponent() && node.BoundAttribute.IsTypeParameterProperty())
                {
                    _children.Add(new ComponentTypeArgumentExtensionNode(node));
                    return;
                }

                _children.Add(new ComponentAttributeExtensionNode(node));
            }
示例#16
0
            public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
            {
                // Each 'tag helper property' belongs to a specific tag helper. We want to handle
                // the cases for components, but leave others alone. This allows our other passes
                // to handle those cases.
                if (!node.TagHelper.IsComponentTagHelper())
                {
                    _children.Add(node);
                    return;
                }

                // Another special case here - this might be a type argument. These don't represent 'real' parameters
                // that get passed to the component, it needs special code generation support.
                if (node.TagHelper.IsGenericTypedComponent() && node.BoundAttribute.IsTypeParameterProperty())
                {
                    _children.Add(new ComponentTypeArgumentIntermediateNode(node));
                    return;
                }

                // Another special case here -- this might be a 'Context' parameter, which specifies the name
                // for lambda parameter for parameterized child content
                if (node.BoundAttribute.IsChildContentParameterNameProperty())
                {
                    // Check for each child content with a parameter name, that the parameter name is specified
                    // with literal text. For instance, the following is not allowed and should generate a diagnostic.
                    //
                    // <MyComponent Context="@Foo()">...</MyComponent>
                    if (TryGetAttributeStringContent(node, out var parameterName))
                    {
                        _component.ChildContentParameterName = parameterName;
                        return;
                    }

                    // The parameter name is invalid.
                    _component.Diagnostics.Add(ComponentDiagnosticFactory.Create_ChildContentHasInvalidParameterOnComponent(node.Source, node.AttributeName, _component.TagName));
                    return;
                }

                _children.Add(new ComponentAttributeIntermediateNode(node));
            }
示例#17
0
        private bool TryGetFormatNode(
            IntermediateNode node,
            TagHelperPropertyIntermediateNode attributeNode,
            string valueAttributeName,
            out TagHelperPropertyIntermediateNode formatNode)
        {
            for (var i = 0; i < node.Children.Count; i++)
            {
                var child = node.Children[i] as TagHelperPropertyIntermediateNode;
                if (child != null &&
                    child.TagHelper != null &&
                    child.TagHelper == attributeNode.TagHelper &&
                    child.AttributeName == "format-" + valueAttributeName)
                {
                    formatNode = child;
                    return(true);
                }
            }

            formatNode = null;
            return(false);
        }
示例#18
0
 private static IntermediateToken GetAttributeContent(TagHelperPropertyIntermediateNode 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.
         var content = "\"" + ((IntermediateToken)htmlContentNode.Children.Single()).Content + "\"";
         return(new IntermediateToken()
         {
             Content = content, Kind = TokenKind.CSharp,
         });
     }
     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());
     }
     else
     {
         // This is the common case for 'mixed' content
         return((IntermediateToken)node.Children.Single());
     }
 }
    public DefaultTagHelperPropertyIntermediateNode(TagHelperPropertyIntermediateNode propertyNode)
    {
        if (propertyNode == null)
        {
            throw new ArgumentNullException(nameof(propertyNode));
        }

        AttributeName      = propertyNode.AttributeName;
        AttributeStructure = propertyNode.AttributeStructure;
        BoundAttribute     = propertyNode.BoundAttribute;
        IsIndexerNameMatch = propertyNode.IsIndexerNameMatch;
        Source             = propertyNode.Source;
        TagHelper          = propertyNode.TagHelper;

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

        for (var i = 0; i < propertyNode.Diagnostics.Count; i++)
        {
            Diagnostics.Add(propertyNode.Diagnostics[i]);
        }
    }
示例#20
0
        // Attempts to compute the attribute names that should be used for an instance of 'bind'.
        private bool TryComputeAttributeNames(
            IntermediateNode parent,
            TagHelperPropertyIntermediateNode node,
            string attributeName,
            out string valueAttributeName,
            out string changeAttributeName,
            out BoundAttributeDescriptor valueAttribute,
            out BoundAttributeDescriptor changeAttribute)
        {
            valueAttribute  = null;
            changeAttribute = null;

            // Even though some of our 'bind' tag helpers specify the attribute names, they
            // should still satisfy one of the valid syntaxes.
            if (!TryParseBindAttribute(attributeName, out valueAttributeName, out changeAttributeName))
            {
                return(false);
            }

            // The tag helper specifies attribute names, they should win.
            //
            // This handles cases like <input type="text" bind="@Foo" /> where the tag helper is
            // generated to match a specific tag and has metadata that identify the attributes.
            //
            // We expect 1 bind tag helper per-node.
            valueAttributeName  = node.TagHelper.GetValueAttributeName() ?? valueAttributeName;
            changeAttributeName = node.TagHelper.GetChangeAttributeName() ?? changeAttributeName;

            // We expect 0-1 components per-node.
            var componentTagHelper = (parent as ComponentIntermediateNode)?.Component;

            if (componentTagHelper == null)
            {
                // If it's not a component node then there isn't too much else to figure out.
                return(attributeName != null && changeAttributeName != null);
            }

            // If this is a component, we need an attribute name for the value.
            if (attributeName == null)
            {
                return(false);
            }

            // If this is a component, then we can infer '<PropertyName>Changed' as the name
            // of the change event.
            if (changeAttributeName == null)
            {
                changeAttributeName = valueAttributeName + "Changed";
            }

            for (var i = 0; i < componentTagHelper.BoundAttributes.Count; i++)
            {
                var attribute = componentTagHelper.BoundAttributes[i];

                if (string.Equals(valueAttributeName, attribute.Name))
                {
                    valueAttribute = attribute;
                }

                if (string.Equals(changeAttributeName, attribute.Name))
                {
                    changeAttribute = attribute;
                }
            }

            return(true);
        }
示例#21
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 });
            }
        }
示例#22
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:
            //
            // 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>()
            {
                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 (node.IsDirectiveAttribute && attributeName.StartsWith("@"))
            {
                // Directive attributes start with a "@" but we don't want that to be included in the output attribute name.
                // E.g, <input @onclick="..." /> should result in the creation of 'onclick' attribute.
                attributeName = attributeName.Substring(1);
            }
            if (parent is MarkupElementIntermediateNode)
            {
                var result = new HtmlAttributeIntermediateNode()
                {
                    Annotations =
                    {
                        [ComponentMetadata.Common.OriginalAttributeName] = node.AttributeName,
                    },
                    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.AttributeName,
                    },
                };

                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);
            }
        }
示例#23
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 });
            }
        }
示例#24
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 VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
 {
     // Don't rewrite inside of attributes
 }
 public virtual void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
 {
     VisitDefault(node);
 }
示例#27
0
 public override void VisitTagHelperProperty(TagHelperPropertyIntermediateNode node)
 {
     WriteContentNode(node, node.AttributeName, node.BoundAttribute.DisplayName, string.Format("HtmlAttributeValueStyle.{0}", node.AttributeStructure));
 }
        private IntermediateNode RewriteUsage(ClassDeclarationIntermediateNode classNode, IntermediateNode parent, TagHelperPropertyIntermediateNode node)
        {
            // If we can't get a nonempty attribute name, do nothing because there will
            // already be a diagnostic for empty values
            var identifierToken = DetermineIdentifierToken(node);

            if (identifierToken == null)
            {
                return(node);
            }

            // 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(identifierToken, componentTagHelper.GetTypeName()));
            }
            else
            {
                return(new ReferenceCaptureIntermediateNode(identifierToken));
            }
        }