Пример #1
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);
            }
        }
            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);
                }
            }