Пример #1
0
        private void ProcessAttributes(TagHelperIntermediateNode node)
        {
            for (var i = node.Children.Count - 1; i >= 0; i--)
            {
                if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode)
                {
                    if (HasComplexChildContent(propertyNode))
                    {
                        node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
                                                 propertyNode,
                                                 propertyNode.AttributeName));
                        node.Children.RemoveAt(i);
                        continue;
                    }

                    node.Children[i] = new ComponentAttributeExtensionNode(propertyNode);
                }
                else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode)
                {
                    if (HasComplexChildContent(htmlNode))
                    {
                        node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
                                                 htmlNode,
                                                 htmlNode.AttributeName));
                        node.Children.RemoveAt(i);
                        continue;
                    }
                }
            }
        }
Пример #2
0
            public void VisitExtension(ComponentChildContentIntermediateNode node)
            {
                // Check that each child content has a unique parameter name within its scope. This is important
                // because the parameter name can be implict, and it doesn't work well when nested.
                if (node.IsParameterized)
                {
                    for (var i = 0; i < Ancestors.Count - 1; i++)
                    {
                        var ancestor = Ancestors[i] as ComponentChildContentIntermediateNode;
                        if (ancestor != null &&
                            ancestor.IsParameterized &&
                            string.Equals(node.ParameterName, ancestor.ParameterName, StringComparison.Ordinal))
                        {
                            // Duplicate name. We report an error because this will almost certainly also lead to an error
                            // from the C# compiler that's way less clear.
                            node.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentRepeatedParameterName(
                                                     node.Source,
                                                     node,
                                                     (ComponentExtensionNode)Ancestors[0],       // Encosing component
                                                     ancestor,                                   // conflicting child content node
                                                     (ComponentExtensionNode)Ancestors[i + 1])); // Encosing component of conflicting child content node
                        }
                    }
                }

                base.VisitDefault(node);
            }
Пример #3
0
            private ComponentChildContentIntermediateNode RewriteChildContent(BoundAttributeDescriptor attribute, SourceSpan?source, IntermediateNodeCollection children)
            {
                var childContent = new ComponentChildContentIntermediateNode()
                {
                    BoundAttribute = attribute,
                    Source         = source,
                    TypeName       = attribute?.TypeName ?? BlazorApi.RenderFragment.FullTypeName,
                };

                // There are two cases here:
                // 1. Implicit child content - the children will be non-taghelper nodes, just accept them
                // 2. Explicit child content - the children will be various tag helper nodes, that need special processing.
                for (var i = 0; i < children.Count; i++)
                {
                    var child = children[i];
                    if (child is TagHelperBodyIntermediateNode body)
                    {
                        // The body is all of the content we want to render, the rest of the childen will
                        // be the attributes.
                        for (var j = 0; j < body.Children.Count; j++)
                        {
                            childContent.Children.Add(body.Children[j]);
                        }
                    }
                    else if (child is TagHelperPropertyIntermediateNode property)
                    {
                        if (property.BoundAttribute.Kind == BlazorMetadata.ChildContent.TagHelperKind)
                        {
                            // 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><ChildContent Context="@Foo()">...</ChildContent></MyComponent>
                            if (TryGetAttributeStringContent(property, out var parameterName))
                            {
                                childContent.ParameterName = parameterName;
                                continue;
                            }

                            // The parameter name is invalid.
                            childContent.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidParameter(property.Source, property.AttributeName, attribute.Name));
                            continue;
                        }

                        // This is an unrecognized attribute, this is possible if you try to do something like put 'ref' on a child content.
                        childContent.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidAttribute(property.Source, property.AttributeName, attribute.Name));
                    }
                    else if (child is TagHelperHtmlAttributeIntermediateNode a)
                    {
                        // This is an HTML attribute on a child content.
                        childContent.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentHasInvalidAttribute(a.Source, a.AttributeName, attribute.Name));
                    }
                    else
                    {
                        // This is some other kind of node (likely an implicit child content)
                        childContent.Children.Add(child);
                    }
                }

                return(childContent);
            }
Пример #4
0
            public void VisitExtension(HtmlElementIntermediateNode node)
            {
                // Disallow <script> in components as per #552
                if (string.Equals(node.TagName, "script", StringComparison.OrdinalIgnoreCase))
                {
                    for (var i = 0; i < node.Children.Count; i++)
                    {
                        // We allow you to suppress this error like:
                        // <script suppress-error="BL9992" />
                        var attribute = node.Children[i] as HtmlAttributeIntermediateNode;
                        if (attribute != null &&
                            attribute.AttributeName == "suppress-error" &&
                            attribute.Children.Count == 1 &&
                            attribute.Children[0] is HtmlAttributeValueIntermediateNode value &&
                            value.Children.Count == 1 &&
                            value.Children[0] is IntermediateToken token &&
                            token.IsHtml &&
                            string.Equals(token.Content, "BL9992", StringComparison.Ordinal))
                        {
                            node.Children.RemoveAt(i);
                            return;
                        }
                    }

                    var diagnostic = BlazorDiagnosticFactory.Create_DisallowedScriptTag(node.Source);
                    node.Diagnostics.Add(diagnostic);
                }

                base.VisitDefault(node);
            }
Пример #5
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>());
            }
        }
Пример #6
0
        protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
        {
            var @namespace = documentNode.FindPrimaryNamespace();
            var @class     = documentNode.FindPrimaryClass();

            if (@namespace == null || @class == null)
            {
                // Nothing to do, bail. We can't function without the standard structure.
                return;
            }

            // For each component *usage* we need to rewrite the tag helper node to map to the relevant component
            // APIs.
            var nodes = documentNode.FindDescendantNodes <TagHelperIntermediateNode>();

            for (var i = 0; i < nodes.Count; i++)
            {
                var node = nodes[i];
                if (node.TagHelpers.Count > 1)
                {
                    node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
                }

                RewriteUsage(node, node.TagHelpers[0]);
            }
        }
Пример #7
0
 private void ProcessAttributes(TagHelperIntermediateNode node)
 {
     for (var i = node.Children.Count - 1; i >= 0; i--)
     {
         if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode)
         {
             if (TrySimplifyContent(propertyNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
             {
                 node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
                                          propertyNode,
                                          propertyNode.AttributeName));
                 node.Children.RemoveAt(i);
                 continue;
             }
         }
         else if (node.Children[i] is TagHelperHtmlAttributeIntermediateNode htmlNode)
         {
             if (TrySimplifyContent(htmlNode) && node.TagHelpers.Any(t => t.IsComponentTagHelper()))
             {
                 node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
                                          htmlNode,
                                          htmlNode.AttributeName));
                 node.Children.RemoveAt(i);
                 continue;
             }
         }
     }
 }
Пример #8
0
        public sealed override void WriteCSharpCodeAttributeValue(CodeRenderingContext context, CSharpCodeAttributeValueIntermediateNode node)
        {
            // We used to support syntaxes like <elem onsomeevent=@{ /* some C# code */ } /> but this is no longer the
            // case.
            //
            // We provide an error for this case just to be friendly.
            var content = string.Join("", node.Children.OfType <IntermediateToken>().Select(t => t.Content));

            context.Diagnostics.Add(BlazorDiagnosticFactory.Create_CodeBlockInAttribute(node.Source, content));
            return;
        }
Пример #9
0
        private void RewriteUsage(TagHelperIntermediateNode node, TagHelperDescriptor tagHelper)
        {
            // Ignore Kind here. Some versions of Razor have a bug in the serializer that ignores it.

            // We need to surround the contents of the node with open and close nodes to ensure the component
            // is scoped correctly.
            node.Children.Insert(0, new ComponentOpenExtensionNode()
            {
                TypeName = tagHelper.GetTypeName(),
            });

            for (var i = node.Children.Count - 1; i >= 0; i--)
            {
                if (node.Children[i] is TagHelperBodyIntermediateNode bodyNode)
                {
                    // Replace with a node that we recognize so that it we can do proper scope tracking.
                    //
                    // Note that we force the body node to be last, this is done to push it after the
                    // attribute nodes. This gives us the ordering we want for the render tree.
                    node.Children.RemoveAt(i);
                    node.Children.Add(new ComponentBodyExtensionNode(bodyNode)
                    {
                        TagMode = node.TagMode,
                        TagName = node.TagName,
                    });
                }
            }

            node.Children.Add(new ComponentCloseExtensionNode());

            // Now we need to rewrite any set property or HTML nodes to call the appropriate AddAttribute api.
            for (var i = node.Children.Count - 1; i >= 0; i--)
            {
                if (node.Children[i] is TagHelperPropertyIntermediateNode propertyNode &&
                    propertyNode.TagHelper == tagHelper)
                {
                    // We don't support 'complex' content for components (mixed C# and markup) right now.
                    // It's not clear yet if Blazor will have a good scenario to use these constructs.
                    //
                    // This is where a lot of the complexity in the Razor/TagHelpers model creeps in and we
                    // might be able to avoid it if these features aren't needed.
                    if (HasComplexChildContent(propertyNode))
                    {
                        node.Diagnostics.Add(BlazorDiagnosticFactory.Create_UnsupportedComplexContent(
                                                 propertyNode,
                                                 propertyNode.AttributeName));
                        node.Children.RemoveAt(i);
                        continue;
                    }

                    node.Children[i] = new ComponentAttributeExtensionNode(propertyNode);
                }
Пример #10
0
        private void ProcessDuplicates(TagHelperIntermediateNode node)
        {
            // Reverse order because we will remove nodes.
            //
            // Each 'property' node could be duplicated if there are multiple tag helpers that match that
            // particular attribute. This is likely to happen when a component also defines something like
            // OnClick. We want to remove the 'onclick' and let it fall back to be handled by the component.
            for (var i = node.Children.Count - 1; i >= 0; i--)
            {
                var attributeNode = node.Children[i] as ComponentAttributeExtensionNode;
                if (attributeNode != null &&
                    attributeNode.TagHelper != null &&
                    attributeNode.TagHelper.IsEventHandlerTagHelper())
                {
                    for (var j = 0; j < node.Children.Count; j++)
                    {
                        var duplicate = node.Children[j] as ComponentAttributeExtensionNode;
                        if (duplicate != null &&
                            duplicate.TagHelper != null &&
                            duplicate.TagHelper.IsComponentTagHelper() &&
                            duplicate.AttributeName == attributeNode.AttributeName)
                        {
                            // Found a duplicate - remove the 'fallback' in favor of the
                            // more specific tag helper.
                            node.Children.RemoveAt(i);
                            node.TagHelpers.Remove(attributeNode.TagHelper);
                            break;
                        }
                    }
                }
            }

            // If we still have duplicates at this point then they are genuine conflicts.
            var duplicates = node.Children
                             .OfType <ComponentAttributeExtensionNode>()
                             .Where(p => p.TagHelper?.IsEventHandlerTagHelper() ?? false)
                             .GroupBy(p => p.AttributeName)
                             .Where(g => g.Count() > 1);

            foreach (var duplicate in duplicates)
            {
                node.Diagnostics.Add(BlazorDiagnosticFactory.CreateEventHandler_Duplicates(
                                         node.Source,
                                         duplicate.Key,
                                         duplicate.ToArray()));
                foreach (var property in duplicate)
                {
                    node.Children.Remove(property);
                }
            }
        }
Пример #11
0
        protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
        {
            var @namespace = documentNode.FindPrimaryNamespace();
            var @class     = documentNode.FindPrimaryClass();

            if (@namespace == null || @class == null)
            {
                // Nothing to do, bail. We can't function without the standard structure.
                return;
            }

            // For each component *usage* we need to rewrite the tag helper node to map to the relevant component
            // APIs.
            var references = documentNode.FindDescendantReferences <TagHelperIntermediateNode>();

            for (var i = 0; i < references.Count; i++)
            {
                var reference = references[i];
                var node      = (TagHelperIntermediateNode)reference.Node;

                var count = 0;
                for (var j = 0; j < node.TagHelpers.Count; j++)
                {
                    if (node.TagHelpers[j].IsComponentTagHelper())
                    {
                        // Only allow a single component tag helper per element. If there are multiple, we'll just consider
                        // the first one and ignore the others.
                        if (count++ > 1)
                        {
                            node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
                            break;
                        }
                    }
                }

                if (count >= 1)
                {
                    reference.Replace(RewriteAsComponent(node, node.TagHelpers.First(t => t.IsComponentTagHelper())));
                }
                else if (node.TagHelpers.Any(t => t.IsChildContentTagHelper()))
                {
                    // Ignore, this will be handled when we rewrite the parent.
                }
                else
                {
                    reference.Replace(RewriteAsElement(node));
                }
            }
        }
Пример #12
0
        protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
        {
            var visitor = new Visitor();

            visitor.Visit(documentNode);

            for (var i = 0; i < visitor.Candidates.Count; i++)
            {
                var candidate = visitor.Candidates[i];
                candidate.Parent.Diagnostics.Add(BlazorDiagnosticFactory.Create_TemplateInvalidLocation(candidate.Node.Source));

                // Remove the offending node since we don't know how to render it. This means that the user won't get C#
                // completion at this location, which is fine because it's inside an HTML attribute.
                candidate.Remove();
            }
        }
Пример #13
0
        private static 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 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));
            }

            // 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)),
                });
            }
        }
Пример #14
0
            public void VisitExtension(ComponentExtensionNode node)
            {
                // Check for properties that are set by both element contents (body) and the attribute itself.
                foreach (var childContent in node.ChildContents)
                {
                    foreach (var attribute in node.Attributes)
                    {
                        if (attribute.AttributeName == childContent.AttributeName)
                        {
                            node.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentSetByAttributeAndBody(
                                                     attribute.Source,
                                                     attribute.AttributeName));
                        }
                    }
                }

                base.VisitDefault(node);
            }
Пример #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;
                }

                // 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(BlazorDiagnosticFactory.Create_ChildContentHasInvalidParameterOnComponent(node.Source, node.AttributeName, _component.TagName));
                    return;
                }

                _children.Add(new ComponentAttributeExtensionNode(node));
            }
Пример #16
0
            private static bool ValidateTypeArguments(ComponentExtensionNode node, Dictionary <string, GenericTypeNameRewriter.Binding> bindings)
            {
                var missing = new List <BoundAttributeDescriptor>();

                foreach (var binding in bindings)
                {
                    if (binding.Value.Node == null || string.IsNullOrWhiteSpace(binding.Value.Content))
                    {
                        missing.Add(binding.Value.Attribute);
                    }
                }

                if (missing.Count > 0)
                {
                    // We add our own error for this because its likely the user will see other errors due
                    // to incorrect codegen without the types. Our errors message will pretty clearly indicate
                    // what to do, whereas the other errors might be confusing.
                    node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentMissingTypeArgument(node.Source, node, missing));
                    return(false);
                }

                return(true);
            }
Пример #17
0
        protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
        {
            var @namespace = documentNode.FindPrimaryNamespace();
            var @class     = documentNode.FindPrimaryClass();

            if (@namespace == null || @class == null)
            {
                // Nothing to do, bail. We can't function without the standard structure.
                return;
            }

            // For each component *usage* we need to rewrite the tag helper node to map to the relevant component
            // APIs.
            var nodes = documentNode.FindDescendantNodes <TagHelperIntermediateNode>();

            for (var i = 0; i < nodes.Count; i++)
            {
                var count = 0;
                var node  = nodes[i];
                for (var j = 0; j < node.TagHelpers.Count; j++)
                {
                    if (node.TagHelpers[j].IsComponentTagHelper())
                    {
                        // Only allow a single component tag helper per element. We also have some *special* tag helpers
                        // and they should have already been processed by now.
                        if (count++ > 1)
                        {
                            node.Diagnostics.Add(BlazorDiagnosticFactory.Create_MultipleComponents(node.Source, node.TagName, node.TagHelpers));
                            break;
                        }

                        RewriteUsage(node, node.TagHelpers[j]);
                    }
                }
            }
        }
Пример #18
0
        public void CloseScope(CodeRenderingContext context, string tagName, bool isComponent, SourceSpan?source)
        {
            if (_stack.Count == 0)
            {
                var diagnostic = BlazorDiagnosticFactory.Create_UnexpectedClosingTag(source ?? SourceSpan.Undefined, tagName);
                throw new RazorCompilerException(diagnostic);
            }

            var currentScope = _stack.Pop();

            if (!tagName.Equals(currentScope.TagName, StringComparison.Ordinal))
            {
                var diagnostic = BlazorDiagnosticFactory.Create_MismatchedClosingTag(source, currentScope.TagName, tagName);
                throw new RazorCompilerException(diagnostic);
            }

            // Note: there's no unit test to cover the following, because there's no known way of
            // triggering it from user code (i.e., Razor source code). But the check is here anyway
            // just in case one day it turns out there is some way of causing this error.
            if (isComponent != currentScope.IsComponent)
            {
                var kind         = isComponent ? "component" : "element";
                var expectedKind = currentScope.IsComponent ? "component" : "element";
                var diagnostic   = BlazorDiagnosticFactory.Create_MismatchedClosingTagKind(source, tagName, kind, expectedKind);
                throw new RazorCompilerException(diagnostic);
            }

            // When closing the scope for a component with children, it's time to close the lambda
            if (currentScope.LambdaScope != null)
            {
                currentScope.LambdaScope.Dispose();
                context.CodeWriter.Write(")");
                context.CodeWriter.WriteEndMethodInvocation();
                OffsetBuilderVarNumber(-1);
            }
        }
Пример #19
0
            public override void VisitTagHelperBody(TagHelperBodyIntermediateNode node)
            {
                // Wrap the component's children in a ChildContent node if we have some significant
                // content.
                if (node.Children.Count == 0)
                {
                    return;
                }

                // If we get a single HTML content node containing only whitespace,
                // then this is probably a tag that looks like '<MyComponent>  </MyComponent>
                //
                // We don't want to create a child content for this case, because it can conflict
                // with a child content that's set via an attribute. We don't want the formatting
                // of insigificant whitespace to be annoying when setting attributes directly.
                if (node.Children.Count == 1 && IsIgnorableWhitespace(node.Children[0]))
                {
                    return;
                }

                // From here we fork and behave differently based on whether the component's child content is
                // implicit or explicit.
                //
                // Explict child content will look like: <MyComponent><ChildContent><div>...</div></ChildContent></MyComponent>
                // compared with implicit: <MyComponent><div></div></MyComponent>
                //
                // Using implicit child content:
                // 1. All content is grouped into a single child content lambda, and assiged to the property 'ChildContent'
                //
                // Using explicit child content:
                // 1. All content must be contained within 'child content' elements that are direct children
                // 2. Whitespace outside of 'child content' elements will be ignored (not an error)
                // 3. Non-whitespace outside of 'child content' elements will cause an error
                // 4. All 'child content' elements must match parameters on the component (exception for ChildContent,
                //    which is always allowed.
                // 5. Each 'child content' element will generate its own lambda, and be assigned to the property
                //    that matches the element name.
                if (!node.Children.OfType <TagHelperIntermediateNode>().Any(t => t.TagHelpers.Any(th => th.IsChildContentTagHelper())))
                {
                    // This node has implicit child content. It may or may not have an attribute that matches.
                    var attribute = _component.Component.BoundAttributes
                                    .Where(a => string.Equals(a.Name, BlazorApi.RenderTreeBuilder.ChildContent, StringComparison.Ordinal))
                                    .FirstOrDefault();
                    _children.Add(RewriteChildContent(attribute, node.Source, node.Children));
                    return;
                }

                // OK this node has explicit child content, we can rewrite it by visiting each node
                // in sequence, since we:
                // a) need to rewrite each child content element
                // b) any significant content outside of a child content is an error
                for (var i = 0; i < node.Children.Count; i++)
                {
                    var child = node.Children[i];
                    if (IsIgnorableWhitespace(child))
                    {
                        continue;
                    }

                    if (child is TagHelperIntermediateNode tagHelperNode &&
                        tagHelperNode.TagHelpers.Any(th => th.IsChildContentTagHelper()))
                    {
                        // This is a child content element
                        var attribute = _component.Component.BoundAttributes
                                        .Where(a => string.Equals(a.Name, tagHelperNode.TagName, StringComparison.Ordinal))
                                        .FirstOrDefault();
                        _children.Add(RewriteChildContent(attribute, child.Source, child.Children));
                        continue;
                    }

                    // If we get here then this is significant content inside a component with explicit child content.
                    child.Diagnostics.Add(BlazorDiagnosticFactory.Create_ChildContentMixedWithExplicitChildContent(child.Source, _component));
                    _children.Add(child);
                }

                bool IsIgnorableWhitespace(IntermediateNode n)
                {
                    if (n is HtmlContentIntermediateNode html &&
                        html.Children.Count == 1 &&
                        html.Children[0] is IntermediateToken token &&
                        string.IsNullOrWhiteSpace(token.Content))
                    {
                        return(true);
                    }

                    return(false);
                }
            }
Пример #20
0
            private void RewriteChildren(RazorSourceDocument source, IntermediateNode node)
            {
                // We expect all of the immediate children of a node (together) to comprise
                // a well-formed tree of elements and components.
                var stack = new Stack <IntermediateNode>();

                stack.Push(node);

                // Make a copy, we will clear and rebuild the child collection of this node.
                var children = node.Children.ToArray();

                node.Children.Clear();

                // Due to the way Anglesharp parses HTML (tags at a time) we need to keep track of some state.
                // This handles cases like:
                //
                //  <foo bar="17" baz="@baz" />
                //
                // This will lower like:
                //
                //  HtmlContent <foo bar="17"
                //  HtmlAttribute baz=" - "
                //      CSharpAttributeValue baz
                //  HtmlContent  />
                //
                // We need to consume HTML until we see the 'end tag' for <foo /> and then we can
                // the attributes from the parsed HTML and the CSharpAttribute value.
                var parser     = new HtmlParser(source);
                var attributes = new List <HtmlAttributeIntermediateNode>();

                for (var i = 0; i < children.Length; i++)
                {
                    if (children[i] is HtmlContentIntermediateNode htmlNode)
                    {
                        parser.Push(htmlNode);
                        var tokens = parser.Get();
                        foreach (var token in tokens)
                        {
                            // We have to call this before get. Anglesharp doesn't return the start position
                            // of tokens.
                            var start = parser.GetCurrentLocation();

                            // We have to set the Location explicitly otherwise we would need to include
                            // the token in every call to the parser.
                            parser.SetLocation(token);

                            var end = parser.GetCurrentLocation();

                            if (token.Type == HtmlTokenType.EndOfFile)
                            {
                                break;
                            }

                            switch (token.Type)
                            {
                            case HtmlTokenType.Doctype:
                            {
                                // DocType isn't meaningful in Blazor. We don't process them in the runtime
                                // it wouldn't really mean much anyway since we build a DOM directly rather
                                // than letting the user-agent parse the document.
                                //
                                // For now, <!DOCTYPE html> and similar things will just be skipped by the compiler
                                // unless we come up with something more meaningful to do.
                                break;
                            }

                            case HtmlTokenType.Character:
                            {
                                // Text content
                                var span = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                                stack.Peek().Children.Add(new HtmlContentIntermediateNode()
                                    {
                                        Children =
                                        {
                                            new IntermediateToken()
                                            {
                                                Content = token.Data,
                                                Kind    = TokenKind.Html,
                                                Source  = span,
                                            }
                                        },
                                        Source = span,
                                    });
                                break;
                            }

                            case HtmlTokenType.StartTag:
                            case HtmlTokenType.EndTag:
                            {
                                var tag = token.AsTag();

                                if (token.Type == HtmlTokenType.StartTag)
                                {
                                    var elementNode = new HtmlElementIntermediateNode()
                                    {
                                        TagName = parser.GetTagNameOriginalCasing(tag),
                                        Source  = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex),
                                    };

                                    stack.Peek().Children.Add(elementNode);
                                    stack.Push(elementNode);

                                    for (var j = 0; j < tag.Attributes.Count; j++)
                                    {
                                        // Unfortunately Anglesharp doesn't provide positions for attributes
                                        // so we can't record the spans here.
                                        var attribute = tag.Attributes[j];
                                        stack.Peek().Children.Add(CreateAttributeNode(attribute));
                                    }

                                    for (var j = 0; j < attributes.Count; j++)
                                    {
                                        stack.Peek().Children.Add(attributes[j]);
                                    }
                                    attributes.Clear();
                                }

                                if (tag.IsSelfClosing || VoidElements.Contains(tag.Data))
                                {
                                    // We can't possibly hit an error here since we just added an element node.
                                    stack.Pop();
                                }

                                if (token.Type == HtmlTokenType.EndTag)
                                {
                                    var popped = stack.Pop();
                                    if (stack.Count == 0)
                                    {
                                        // If we managed to 'bottom out' the stack then we have an unbalanced end tag.
                                        // Put back the current node so we don't crash.
                                        stack.Push(popped);

                                        var tagName    = parser.GetTagNameOriginalCasing(token.AsTag());
                                        var span       = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                                        var diagnostic = BlazorDiagnosticFactory.Create_UnexpectedClosingTag(span, tagName);
                                        popped.Children.Add(new HtmlElementIntermediateNode()
                                            {
                                                Diagnostics =
                                                {
                                                    diagnostic,
                                                },
                                                TagName = tagName,
                                                Source  = span,
                                            });
                                    }
                                    else if (!string.Equals(tag.Name, ((HtmlElementIntermediateNode)popped).TagName, StringComparison.OrdinalIgnoreCase))
                                    {
                                        var span       = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                                        var diagnostic = BlazorDiagnosticFactory.Create_MismatchedClosingTag(span, ((HtmlElementIntermediateNode)popped).TagName, token.Data);
                                        popped.Diagnostics.Add(diagnostic);
                                    }
                                    else
                                    {
                                        // Happy path.
                                        //
                                        // We need to compute a new source span because when we found the start tag before we knew
                                        // the end poosition of the tag.
                                        var length = end.AbsoluteIndex - popped.Source.Value.AbsoluteIndex;
                                        popped.Source = new SourceSpan(
                                            popped.Source.Value.FilePath,
                                            popped.Source.Value.AbsoluteIndex,
                                            popped.Source.Value.LineIndex,
                                            popped.Source.Value.CharacterIndex,
                                            length);
                                    }
                                }

                                break;
                            }

                            case HtmlTokenType.Comment:
                                break;

                            default:
                                throw new InvalidCastException($"Unsupported token type: {token.Type.ToString()}");
                            }
                        }
                    }
                    else if (children[i] is HtmlAttributeIntermediateNode htmlAttribute)
                    {
                        // Buffer the attribute for now, it will get written out as part of a tag.
                        attributes.Add(htmlAttribute);
                    }
                    else
                    {
                        // not HTML, or already rewritten.
                        stack.Peek().Children.Add(children[i]);
                    }
                }

                var extraContent = parser.GetUnparsedContent();

                if (!string.IsNullOrEmpty(extraContent))
                {
                    // extra HTML - almost certainly invalid because it couldn't be parsed.
                    var start = parser.GetCurrentLocation();
                    var end   = parser.GetCurrentLocation(extraContent.Length);
                    var span  = new SourceSpan(start, end.AbsoluteIndex - start.AbsoluteIndex);
                    stack.Peek().Children.Add(new HtmlContentIntermediateNode()
                    {
                        Children =
                        {
                            new IntermediateToken()
                            {
                                Content = extraContent,
                                Kind    = TokenKind.Html,
                                Source  = span,
                            }
                        },
                        Diagnostics =
                        {
                            BlazorDiagnosticFactory.Create_InvalidHtmlContent(span, extraContent),
                        },
                        Source = span,
                    });
                }

                while (stack.Count > 1)
                {
                    // not balanced
                    var popped     = (HtmlElementIntermediateNode)stack.Pop();
                    var diagnostic = BlazorDiagnosticFactory.Create_UnclosedTag(popped.Source, popped.TagName);
                    popped.Diagnostics.Add(diagnostic);
                }
            }
Пример #21
0
        private void ProcessDuplicates(TagHelperIntermediateNode node)
        {
            // Reverse order because we will remove nodes.
            //
            // Each 'property' node could be duplicated if there are multiple tag helpers that match that
            // particular attribute. This is common in our approach, which relies on 'fallback' tag helpers
            // that overlap with more specific ones.
            for (var i = node.Children.Count - 1; i >= 0; i--)
            {
                // For each usage of the general 'fallback' bind tag helper, it could duplicate
                // the usage of a more specific one. Look for duplicates and remove the fallback.
                var attributeNode = node.Children[i] as ComponentAttributeExtensionNode;
                if (attributeNode != null &&
                    attributeNode.TagHelper != null &&
                    attributeNode.TagHelper.IsFallbackBindTagHelper())
                {
                    for (var j = 0; j < node.Children.Count; j++)
                    {
                        var duplicate = node.Children[j] as ComponentAttributeExtensionNode;
                        if (duplicate != null &&
                            duplicate.TagHelper != null &&
                            duplicate.TagHelper.IsBindTagHelper() &&
                            duplicate.AttributeName == attributeNode.AttributeName &&
                            !object.ReferenceEquals(attributeNode, duplicate))
                        {
                            // Found a duplicate - remove the 'fallback' in favor of the
                            // more specific tag helper.
                            node.Children.RemoveAt(i);
                            node.TagHelpers.Remove(attributeNode.TagHelper);
                            break;
                        }
                    }
                }

                // Also treat the general <input bind="..." /> as a 'fallback' for that case and remove it.
                // This is a workaround for a limitation where you can't write a tag helper that binds only
                // when a specific attribute is **not** present.
                if (attributeNode != null &&
                    attributeNode.TagHelper != null &&
                    attributeNode.TagHelper.IsInputElementFallbackBindTagHelper())
                {
                    for (var j = 0; j < node.Children.Count; j++)
                    {
                        var duplicate = node.Children[j] as ComponentAttributeExtensionNode;
                        if (duplicate != null &&
                            duplicate.TagHelper != null &&
                            duplicate.TagHelper.IsInputElementBindTagHelper() &&
                            duplicate.AttributeName == attributeNode.AttributeName &&
                            !object.ReferenceEquals(attributeNode, duplicate))
                        {
                            // Found a duplicate - remove the 'fallback' input tag helper in favor of the
                            // more specific tag helper.
                            node.Children.RemoveAt(i);
                            node.TagHelpers.Remove(attributeNode.TagHelper);
                            break;
                        }
                    }
                }
            }

            // If we still have duplicates at this point then they are genuine conflicts.
            var duplicates = node.Children
                             .OfType <ComponentAttributeExtensionNode>()
                             .GroupBy(p => p.AttributeName)
                             .Where(g => g.Count() > 1);

            foreach (var duplicate in duplicates)
            {
                node.Diagnostics.Add(BlazorDiagnosticFactory.CreateBindAttribute_Duplicates(
                                         node.Source,
                                         duplicate.Key,
                                         duplicate.ToArray()));
                foreach (var property in duplicate)
                {
                    node.Children.Remove(property);
                }
            }
        }
Пример #22
0
        protected override void ExecuteCore(RazorCodeDocument codeDocument, DocumentIntermediateNode documentNode)
        {
            if (codeDocument == null)
            {
                throw new ArgumentNullException(nameof(codeDocument));
            }

            if (documentNode == null)
            {
                throw new ArgumentNullException(nameof(documentNode));
            }

            var @namespace = documentNode.FindPrimaryNamespace();
            var @class     = documentNode.FindPrimaryClass();

            if (@namespace == null || @class == null)
            {
                return;
            }

            var directives = documentNode.FindDirectiveReferences(PageDirective.Directive);

            if (directives.Count == 0)
            {
                return;
            }

            // We don't allow @page directives in imports
            for (var i = 0; i < directives.Count; i++)
            {
                var directive = directives[i];
                if (directive.Node.IsImported())
                {
                    directive.Node.Diagnostics.Add(BlazorDiagnosticFactory.CreatePageDirective_CannotBeImported(directive.Node.Source.Value));
                }
            }

            // Insert the attributes 'on-top' of the class declaration, since classes don't directly support attributes.
            var index = 0;

            for (; index < @namespace.Children.Count; index++)
            {
                if (object.ReferenceEquals(@class, @namespace.Children[index]))
                {
                    break;
                }
            }

            for (var i = 0; i < directives.Count; i++)
            {
                var pageDirective = (DirectiveIntermediateNode)directives[i].Node;

                // The parser also adds errors for invalid syntax, we just need to not crash.
                var routeToken = pageDirective.Tokens.FirstOrDefault();

                if (routeToken != null &&
                    routeToken.Content.Length >= 3 &&
                    routeToken.Content[0] == '\"' &&
                    routeToken.Content[1] == '/' &&
                    routeToken.Content[routeToken.Content.Length - 1] == '\"')
                {
                    var template = routeToken.Content.Substring(1, routeToken.Content.Length - 2);
                    @namespace.Children.Insert(index++, new RouteAttributeExtensionNode(template));
                }
                else
                {
                    pageDirective.Diagnostics.Add(BlazorDiagnosticFactory.CreatePageDirective_MustSpecifyRoute(pageDirective.Source));
                }
            }
        }
Пример #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 pair of
            // 'normal' HTML attributes similar to the following transformation.
            //
            // Input:   <MyComponent bind-Value="@currentCount" />
            // Output:  <MyComponent Value ="...<get the value>..." ValueChanged ="... <set the value>..." />
            //
            // This means that the expression that appears inside of 'bind' must be an LValue or else
            // there will be errors. In general the errors that come from C# in this case are good enough
            // to understand the problem.
            //
            // The BindMethods calls are required in this case because to give us a good experience. They
            // use overloading to ensure that can get an Action<object> that will convert and set an arbitrary
            // value.
            //
            // We also assume that the element will be treated as a component for now because
            // multiple passes handle 'special' tag helpers. We have another pass that translates
            // a tag helper node back into 'regular' element when it doesn't have an associated component
            if (!TryComputeAttributeNames(
                    parent,
                    node,
                    node.AttributeName,
                    out var valueAttributeName,
                    out var changeAttributeName,
                    out var valueAttribute,
                    out var changeAttribute))
            {
                // Skip anything we can't understand. It's important that we don't crash, that will bring down
                // the build.
                node.Diagnostics.Add(BlazorDiagnosticFactory.CreateBindAttribute_InvalidSyntax(
                                         node.Source,
                                         node.AttributeName));
                return(new[] { node });
            }

            var original = GetAttributeContent(node);

            if (string.IsNullOrEmpty(original.Content))
            {
                // This can happen in error cases, the parser will already have flagged this
                // as an error, so ignore it.
                return(new[] { node });
            }

            // Look for a matching format node. If we find one then we need to pass the format into the
            // two nodes we generate.
            IntermediateToken format = null;

            if (TryGetFormatNode(
                    parent,
                    node,
                    valueAttributeName,
                    out var formatNode))
            {
                // Don't write the format out as its own attribute, just capture it as a string
                // or expression.
                parent.Children.Remove(formatNode);
                format = GetAttributeContent(formatNode);
            }

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

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

            // Now rewrite the content of the change-handler node. There are two cases we care about
            // here. If it's a component attribute, then don't use the 'BindMethods wrapper. We expect
            // component attributes to always 'match' on type.
            //
            // __value => <code> = __value
            //
            // For general DOM attributes, we need to be able to create a delegate that accepts UIEventArgs
            // so we use BindMethods.SetValueHandler
            //
            // BindMethods.SetValueHandler(__value => <code> = __value, <code>) OR
            // BindMethods.SetValueHandler(__value => <code> = __value, <code>, <format>)
            //
            // Note that the linemappings here are applied to the value attribute, not the change attribute.

            string changeExpressionContent = null;

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

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

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

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

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

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

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

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

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

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

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

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

                return(new[] { valueNode, changeNode });
            }
        }
Пример #24
0
            private void Process(ComponentExtensionNode node)
            {
                // First collect all of the information we have about each type parameter
                //
                // Listing all type parameters that exist
                var bindings = new Dictionary <string, GenericTypeNameRewriter.Binding>();

                foreach (var attribute in node.Component.GetTypeParameters())
                {
                    bindings.Add(attribute.Name, new GenericTypeNameRewriter.Binding()
                    {
                        Attribute = attribute,
                    });
                }

                // Listing all type arguments that have been specified.
                var hasTypeArgumentSpecified = false;

                foreach (var typeArgumentNode in node.TypeArguments)
                {
                    hasTypeArgumentSpecified = true;

                    var binding = bindings[typeArgumentNode.TypeParameterName];
                    binding.Node    = typeArgumentNode;
                    binding.Content = GetContent(typeArgumentNode);
                }

                if (hasTypeArgumentSpecified)
                {
                    // OK this means that the developer has specified at least one type parameter.
                    // Either they specified everything and its OK to rewrite, or its an error.
                    if (ValidateTypeArguments(node, bindings))
                    {
                        RewriteTypeNames(new GenericTypeNameRewriter(bindings), node);
                    }

                    return;
                }

                // OK if we get here that means that no type arguments were specified, so we will try to infer
                // the type.
                //
                // The actual inference is done by the C# compiler, we just emit an a method that represents the
                // use of this component.

                // Since we're generating code in a different namespace, we need to 'global qualify' all of the types
                // to avoid clashes with our generated code.
                RewriteTypeNames(new GlobalQualifiedTypeNameRewriter(bindings.Keys), node);

                //
                // We need to verify that an argument was provided that 'covers' each type parameter.
                //
                // For example, consider a repeater where the generic type is the 'item' type, but the developer has
                // not set the items. We won't be able to do type inference on this and so it will just be nonsense.
                var attributes = node.Attributes.Select(a => a.BoundAttribute).Concat(node.ChildContents.Select(c => c.BoundAttribute));

                foreach (var attribute in attributes)
                {
                    if (attribute == null)
                    {
                        // Will be null for attributes set on the component that don't match a declared component parameter
                        continue;
                    }

                    // Now we need to parse the type name and extract the generic parameters.
                    //
                    // Two cases;
                    // 1. name is a simple identifier like TItem
                    // 2. name contains type parameters like Dictionary<string, TItem>
                    if (!attribute.IsGenericTypedProperty())
                    {
                        continue;
                    }

                    var parsed = SyntaxFactory.ParseTypeName(attribute.TypeName);
                    if (parsed is IdentifierNameSyntax identifier)
                    {
                        bindings.Remove(identifier.ToString());
                    }
                    else
                    {
                        var typeParameters = parsed.DescendantNodesAndSelf().OfType <TypeArgumentListSyntax>().SelectMany(arg => arg.Arguments);
                        foreach (var typeParameter in typeParameters)
                        {
                            bindings.Remove(typeParameter.ToString());
                        }
                    }
                }

                // If any bindings remain then this means we would never be able to infer the arguments of this
                // component usage because the user hasn't set properties that include all of the types.
                if (bindings.Count > 0)
                {
                    // However we still want to generate 'type inference' code because we want the errors to be as
                    // helpful as possible. So let's substitute 'object' for all of those type parameters, and add
                    // an error.
                    RewriteTypeNames(new GenericTypeNameRewriter(bindings), node);

                    node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentTypeInferenceUnderspecified(node.Source, node, node.Component.GetTypeParameters()));
                }

                // Next we need to generate a type inference 'method' node. This represents a method that we will codegen that
                // contains all of the operations on the render tree building. Calling a method to operate on the builder
                // will allow the C# compiler to perform type inference.
                var documentNode = (DocumentIntermediateNode)Ancestors[Ancestors.Count - 1];

                CreateTypeInferenceMethod(documentNode, node, bindings);
            }
Пример #25
0
            private void Process(ComponentExtensionNode node)
            {
                // First collect all of the information we have about each type parameter
                var bindings = new Dictionary <string, GenericTypeNameRewriter.Binding>();

                foreach (var attribute in node.Component.GetTypeParameters())
                {
                    bindings.Add(attribute.Name, new GenericTypeNameRewriter.Binding()
                    {
                        Attribute = attribute,
                    });
                }

                foreach (var typeArgumentNode in node.TypeArguments)
                {
                    var binding = bindings[typeArgumentNode.TypeParameterName];
                    binding.Node    = typeArgumentNode;
                    binding.Content = GetContent(typeArgumentNode);
                }

                // Right now we don't have type inference, so all type arguments are required.
                var missing = new List <BoundAttributeDescriptor>();

                foreach (var binding in bindings)
                {
                    if (binding.Value.Node == null || string.IsNullOrWhiteSpace(binding.Value.Content))
                    {
                        missing.Add(binding.Value.Attribute);
                    }
                }

                if (missing.Count > 0)
                {
                    // We add our own error for this because its likely the user will see other errors due
                    // to incorrect codegen without the types. Our errors message will pretty clearly indicate
                    // what to do, whereas the other errors might be confusing.
                    node.Diagnostics.Add(BlazorDiagnosticFactory.Create_GenericComponentMissingTypeArgument(node.Source, node, missing));
                }

                var rewriter = new GenericTypeNameRewriter(bindings);

                // Rewrite the component type name
                node.TypeName = RewriteTypeName(rewriter, node.TypeName);

                foreach (var attribute in node.Attributes)
                {
                    if (attribute.BoundAttribute?.IsGenericTypedProperty() ?? false && attribute.TypeName != null)
                    {
                        // If we know the type name, then replace any generic type parameter inside it with
                        // the known types.
                        attribute.TypeName = RewriteTypeName(rewriter, attribute.TypeName);
                    }
                }

                foreach (var childContent in node.ChildContents)
                {
                    if (childContent.BoundAttribute?.IsGenericTypedProperty() ?? false && childContent.TypeName != null)
                    {
                        // If we know the type name, then replace any generic type parameter inside it with
                        // the known types.
                        childContent.TypeName = RewriteTypeName(rewriter, childContent.TypeName);
                    }
                }
            }