コード例 #1
0
            public override SyntaxNode VisitMarkupElement(MarkupElementSyntax node)
            {
                if (IsPartOfStartTag(node))
                {
                    // If this element is inside a start tag, it is some sort of malformed case like
                    // <p @do { someattribute=\"btn\"></p>, where the end "p" tag is inside the start "p" tag.
                    // We don't want to do tag helper parsing for this tag.
                    return(base.VisitMarkupElement(node));
                }

                MarkupTagHelperStartTagSyntax tagHelperStart = null;
                MarkupTagHelperEndTagSyntax   tagHelperEnd   = null;
                TagHelperInfo tagHelperInfo = null;

                // Visit the start tag.
                var startTag = (MarkupStartTagSyntax)Visit(node.StartTag);

                if (startTag != null)
                {
                    var tagName = startTag.GetTagName();
                    if (TryRewriteTagHelperStart(startTag, out tagHelperStart, out tagHelperInfo))
                    {
                        // This is a tag helper.
                        if (tagHelperInfo.TagMode == TagMode.SelfClosing || tagHelperInfo.TagMode == TagMode.StartTagOnly)
                        {
                            var tagHelperElement   = SyntaxFactory.MarkupTagHelperElement(tagHelperStart, body: new SyntaxList <RazorSyntaxNode>(), endTag: null);
                            var rewrittenTagHelper = tagHelperElement.WithTagHelperInfo(tagHelperInfo);
                            if (node.Body.Count == 0)
                            {
                                return(rewrittenTagHelper);
                            }

                            // This tag contains a body which needs to be moved to the parent.
                            var rewrittenNodes = SyntaxListBuilder <RazorSyntaxNode> .Create();

                            rewrittenNodes.Add(rewrittenTagHelper);
                            var rewrittenBody = VisitList(node.Body);
                            rewrittenNodes.AddRange(rewrittenBody);

                            return(SyntaxFactory.MarkupElement(startTag: null, body: rewrittenNodes.ToList(), endTag: null));
                        }
                        else if (node.EndTag == null)
                        {
                            // Start tag helper with no corresponding end tag.
                            _errorSink.OnError(
                                RazorDiagnosticFactory.CreateParsing_TagHelperFoundMalformedTagHelper(
                                    new SourceSpan(SourceLocationTracker.Advance(startTag.GetSourceLocation(_source), "<"), tagName.Length),
                                    tagName));
                        }
                        else
                        {
                            // Tag helper start tag. Keep track.
                            var tracker = new TagHelperTracker(_tagHelperPrefix, tagHelperInfo);
                            _trackerStack.Push(tracker);
                        }
                    }
                    else
                    {
                        // Non-TagHelper tag.
                        ValidateParentAllowsPlainStartTag(startTag);

                        if (!startTag.IsSelfClosing() && !startTag.IsVoidElement())
                        {
                            var tracker = new TagTracker(tagName, isTagHelper: false);
                            _trackerStack.Push(tracker);
                        }
                    }
                }

                // Visit body between start and end tags.
                var body = VisitList(node.Body);

                // Visit end tag.
                var endTag = (MarkupEndTagSyntax)Visit(node.EndTag);

                if (endTag != null)
                {
                    var tagName = endTag.GetTagName();
                    if (TryRewriteTagHelperEnd(endTag, out tagHelperEnd))
                    {
                        // This is a tag helper
                        if (startTag == null)
                        {
                            // The end tag helper has no corresponding start tag, create an error.
                            _errorSink.OnError(
                                RazorDiagnosticFactory.CreateParsing_TagHelperFoundMalformedTagHelper(
                                    new SourceSpan(SourceLocationTracker.Advance(endTag.GetSourceLocation(_source), "</"), tagName.Length), tagName));
                        }
                    }
                    else
                    {
                        // Non tag helper end tag.
                        if (startTag == null)
                        {
                            // Standalone end tag. We may need to error if it is not supposed to be here.
                            // If there was a corresponding start tag, we would have already added this error.
                            ValidateParentAllowsPlainEndTag(endTag);
                        }
                        else
                        {
                            // Since a start tag exists, we must already be tracking it.
                            // Pop the stack as we're done with the end tag.
                            _trackerStack.Pop();
                        }
                    }
                }

                if (tagHelperInfo != null)
                {
                    // If we get here it means this element was rewritten as a tag helper.
                    var tagHelperElement = SyntaxFactory.MarkupTagHelperElement(tagHelperStart, body, tagHelperEnd);
                    return(tagHelperElement.WithTagHelperInfo(tagHelperInfo));
                }

                // There was no matching tag helper for this element. Return.
                return(node.Update(startTag, body, endTag));
            }
コード例 #2
0
        private static SyntaxList <RazorSyntaxNode> GetRewrittenMarkupStartTagChildren(MarkupStartTagSyntax node)
        {
            // Rewrites the children of the start tag to look like the legacy syntax tree.
            if (node.IsMarkupTransition)
            {
                var tokens       = node.DescendantNodes().Where(n => n is SyntaxToken token && !token.IsMissing).Cast <SyntaxToken>().ToArray();
                var tokenBuilder = SyntaxListBuilder <SyntaxToken> .Create();

                tokenBuilder.AddRange(tokens, 0, tokens.Length);
                var markupTransition = SyntaxFactory.MarkupTransition(tokenBuilder.ToList()).Green.CreateRed(node, node.Position);
                var spanContext      = node.GetSpanContext();
                if (spanContext != null)
                {
                    markupTransition = markupTransition.WithSpanContext(spanContext);
                }

                var builder = new SyntaxListBuilder(1);
                builder.Add(markupTransition);
                return(new SyntaxList <RazorSyntaxNode>(builder.ToListNode().CreateRed(node, node.Position)));
            }

            SpanContext latestSpanContext = null;
            var         children          = node.Children;
            var         newChildren       = new SyntaxListBuilder(children.Count);
            var         literals          = new List <MarkupTextLiteralSyntax>();

            foreach (var child in children)
            {
                if (child is MarkupTextLiteralSyntax literal)
                {
                    literals.Add(literal);
                    latestSpanContext = literal.GetSpanContext() ?? latestSpanContext;
                }
                else if (child is MarkupMiscAttributeContentSyntax miscContent)
                {
                    foreach (var contentChild in miscContent.Children)
                    {
                        if (contentChild is MarkupTextLiteralSyntax contentLiteral)
                        {
                            literals.Add(contentLiteral);
                            latestSpanContext = contentLiteral.GetSpanContext() ?? latestSpanContext;
                        }
                        else
                        {
                            // Pop stack
                            AddLiteralIfExists();
                            newChildren.Add(contentChild);
                        }
                    }
                }
                else
                {
                    AddLiteralIfExists();
                    newChildren.Add(child);
                }
            }

            AddLiteralIfExists();

            return(new SyntaxList <RazorSyntaxNode>(newChildren.ToListNode().CreateRed(node, node.Position)));

            void AddLiteralIfExists()
            {
                if (literals.Count > 0)
                {
                    var mergedLiteral = SyntaxUtilities.MergeTextLiterals(literals.ToArray());
                    mergedLiteral = mergedLiteral.WithSpanContext(latestSpanContext);
                    literals.Clear();
                    latestSpanContext = null;
                    newChildren.Add(mergedLiteral);
                }
            }
        }
コード例 #3
0
        private static TryParseResult TryParseAttribute(
            string tagName,
            MarkupAttributeBlockSyntax attributeBlock,
            IEnumerable <TagHelperDescriptor> descriptors,
            ErrorSink errorSink,
            HashSet <string> processedBoundAttributeNames)
        {
            // Have a name now. Able to determine correct isBoundNonStringAttribute value.
            var result = CreateTryParseResult(attributeBlock.Name.GetContent(), descriptors, processedBoundAttributeNames);

            if (attributeBlock.ValuePrefix == null)
            {
                // We are purposefully not persisting NoQuotes even for unbound attributes because it is still possible to
                // rewrite the values that introduces a space like in UrlResolutionTagHelper.
                // The other case is it could be an expression, treat NoQuotes and DoubleQuotes equivalently. We purposefully do not persist NoQuotes
                // ValueStyles at code generation time to protect users from rendering dynamic content with spaces
                // that can break attributes.
                // Ex: <tag my-attribute=@value /> where @value results in the test "hello world".
                // This way, the above code would render <tag my-attribute="hello world" />.
                result.AttributeStructure = AttributeStructure.DoubleQuotes;
            }
            else
            {
                var lastToken = attributeBlock.ValuePrefix.GetLastToken();
                switch (lastToken.Kind)
                {
                case SyntaxKind.DoubleQuote:
                    result.AttributeStructure = AttributeStructure.DoubleQuotes;
                    break;

                case SyntaxKind.SingleQuote:
                    result.AttributeStructure = AttributeStructure.SingleQuotes;
                    break;

                default:
                    result.AttributeStructure = AttributeStructure.Minimized;
                    break;
                }
            }

            var attributeValue = attributeBlock.Value;

            if (attributeValue == null)
            {
                var builder = SyntaxListBuilder <RazorSyntaxNode> .Create();

                // Add a marker for attribute value when there are no quotes like, <p class= >
                builder.Add(SyntaxFactory.MarkupTextLiteral(new SyntaxList <SyntaxToken>()));

                attributeValue = SyntaxFactory.GenericBlock(builder.ToList());
            }
            var rewrittenValue = RewriteAttributeValue(result, attributeValue);

            if (result.IsDirectiveAttribute)
            {
                // Directive attributes have a different syntax.
                result.RewrittenAttribute = RewriteToDirectiveAttribute(attributeBlock, result, rewrittenValue);

                return(result);
            }
            else
            {
                var rewritten = SyntaxFactory.MarkupTagHelperAttribute(
                    attributeBlock.NamePrefix,
                    attributeBlock.Name,
                    attributeBlock.NameSuffix,
                    attributeBlock.EqualsToken,
                    attributeBlock.ValuePrefix,
                    rewrittenValue,
                    attributeBlock.ValueSuffix);

                rewritten = rewritten.WithTagHelperAttributeInfo(
                    new TagHelperAttributeInfo(result.AttributeName, parameterName: null, result.AttributeStructure, result.IsBoundAttribute, isDirectiveAttribute: false));


                result.RewrittenAttribute = rewritten;

                return(result);
            }
        }
コード例 #4
0
        public static MarkupTagHelperStartTagSyntax Rewrite(
            string tagName,
            RazorParserFeatureFlags featureFlags,
            MarkupStartTagSyntax startTag,
            TagHelperBinding bindingResult,
            ErrorSink errorSink,
            RazorSourceDocument source)
        {
            var processedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            var attributes       = startTag.Attributes;
            var attributeBuilder = SyntaxListBuilder <RazorSyntaxNode> .Create();

            for (var i = 0; i < startTag.Attributes.Count; i++)
            {
                var            isMinimized           = false;
                var            attributeNameLocation = SourceLocation.Undefined;
                var            child = startTag.Attributes[i];
                TryParseResult result;
                if (child is MarkupAttributeBlockSyntax attributeBlock)
                {
                    attributeNameLocation = attributeBlock.Name.GetSourceLocation(source);
                    result = TryParseAttribute(
                        tagName,
                        attributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    attributeBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock)
                {
                    isMinimized           = true;
                    attributeNameLocation = minimizedAttributeBlock.Name.GetSourceLocation(source);
                    result = TryParseMinimizedAttribute(
                        tagName,
                        minimizedAttributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    attributeBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMiscAttributeContentSyntax miscContent)
                {
                    foreach (var contentChild in miscContent.Children)
                    {
                        if (contentChild is CSharpCodeBlockSyntax codeBlock)
                        {
                            // TODO: Accept more than just Markup attributes: https://github.com/aspnet/Razor/issues/96.
                            // Something like:
                            // <input @checked />
                            var location   = new SourceSpan(codeBlock.GetSourceLocation(source), codeBlock.FullWidth);
                            var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelpersCannotHaveCSharpInTagDeclaration(location, tagName);
                            errorSink.OnError(diagnostic);
                            break;
                        }
                        else
                        {
                            // If the original span content was whitespace it ultimately means the tag
                            // that owns this "attribute" is malformed and is expecting a user to type a new attribute.
                            // ex: <myTH class="btn"| |
                            var literalContent = contentChild.GetContent();
                            if (!string.IsNullOrWhiteSpace(literalContent))
                            {
                                var location   = contentChild.GetSourceSpan(source);
                                var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelperAttributeListMustBeWellFormed(location);
                                errorSink.OnError(diagnostic);
                                break;
                            }
                        }
                    }

                    result = null;
                }
                else
                {
                    result = null;
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result == null)
                {
                    // Error occurred while parsing the attribute. Don't try parsing the rest to avoid misleading errors.
                    for (var j = i; j < startTag.Attributes.Count; j++)
                    {
                        attributeBuilder.Add(startTag.Attributes[j]);
                    }

                    break;
                }

                // Check if it's a non-boolean bound attribute that is minimized or if it's a bound
                // non-string attribute that has null or whitespace content.
                var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute;
                if ((isMinimized &&
                     result.IsBoundAttribute &&
                     !isValidMinimizedAttribute) ||
                    (!isMinimized &&
                     result.IsBoundNonStringAttribute &&
                     string.IsNullOrWhiteSpace(GetAttributeValueContent(result.RewrittenAttribute))))
                {
                    var errorLocation    = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var propertyTypeName = GetPropertyType(result.AttributeName, bindingResult.Descriptors);
                    var diagnostic       = RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(errorLocation, result.AttributeName, tagName, propertyTypeName);
                    errorSink.OnError(diagnostic);
                }

                // Check if the attribute was a prefix match for a tag helper dictionary property but the
                // dictionary key would be the empty string.
                if (result.IsMissingDictionaryKey)
                {
                    var errorLocation = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var diagnostic    = RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(errorLocation, result.AttributeName, tagName);
                    errorSink.OnError(diagnostic);
                }
            }

            if (attributeBuilder.Count > 0)
            {
                // This means we rewrote something. Use the new set of attributes.
                attributes = attributeBuilder.ToList();
            }

            var tagHelperStartTag = SyntaxFactory.MarkupTagHelperStartTag(
                startTag.OpenAngle, startTag.Bang, startTag.Name, attributes, startTag.ForwardSlash, startTag.CloseAngle);

            return(tagHelperStartTag.WithSpanContext(startTag.GetSpanContext()));
        }
コード例 #5
0
    private static SyntaxNode CreateNode(IEnumerable <TNode> nodes)
    {
        if (nodes == null)
        {
            return(null);
        }

        var builder = (nodes is ICollection <TNode> collection) ? new SyntaxListBuilder <TNode>(collection.Count) : SyntaxListBuilder <TNode> .Create();

        foreach (var node in nodes)
        {
            builder.Add(node);
        }

        return(builder.ToList().Node);
    }
コード例 #6
0
        private static SyntaxNode?CreateNode(IEnumerable <TNode>?nodes)
        {
            if (nodes == null)
            {
                return(null);
            }

            var collection = nodes as ICollection <TNode>;
            var builder    = (collection != null) ? new SyntaxListBuilder <TNode>(collection.Count) : SyntaxListBuilder <TNode> .Create();

            foreach (TNode node in nodes)
            {
                builder.Add(node);
            }

            return(builder.ToList().Node);
        }
コード例 #7
0
            private void VisitAttributeValue(SyntaxNode node)
            {
                if (node == null)
                {
                    return;
                }

                IReadOnlyList <SyntaxNode> children = node.ChildNodes();
                var position = node.Position;

                if (children.First() is MarkupBlockSyntax markupBlock &&
                    markupBlock.Children.Count == 2 &&
                    markupBlock.Children[0] is MarkupTextLiteralSyntax &&
                    markupBlock.Children[1] is MarkupEphemeralTextLiteralSyntax)
                {
                    // This is a special case when we have an attribute like attr="@@foo".
                    // In this case, we want the foo to be written out as HtmlContent and not HtmlAttributeValue.
                    Visit(markupBlock);
                    children = children.Skip(1).ToList();
                    position = children.Count > 0 ? children[0].Position : position;
                }

                if (children.All(c => c is MarkupLiteralAttributeValueSyntax))
                {
                    var literalAttributeValueNodes = children.Cast <MarkupLiteralAttributeValueSyntax>().ToArray();
                    var valueTokens = SyntaxListBuilder <SyntaxToken> .Create();

                    for (var i = 0; i < literalAttributeValueNodes.Length; i++)
                    {
                        var mergedValue = MergeAttributeValue(literalAttributeValueNodes[i]);
                        valueTokens.AddRange(mergedValue.LiteralTokens);
                    }
                    var rewritten = SyntaxFactory.MarkupTextLiteral(valueTokens.ToList()).Green.CreateRed(node.Parent, position);
                    Visit(rewritten);
                }
                else if (children.All(c => c is MarkupTextLiteralSyntax))
                {
                    var builder = SyntaxListBuilder <SyntaxToken> .Create();

                    var markupLiteralArray = children.Cast <MarkupTextLiteralSyntax>();
                    foreach (var literal in markupLiteralArray)
                    {
                        builder.AddRange(literal.LiteralTokens);
                    }
                    var rewritten = SyntaxFactory.MarkupTextLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
                    Visit(rewritten);
                }
                else if (children.All(c => c is CSharpExpressionLiteralSyntax))
                {
                    var builder = SyntaxListBuilder <SyntaxToken> .Create();

                    var         expressionLiteralArray = children.Cast <CSharpExpressionLiteralSyntax>();
                    SpanContext context = null;
                    foreach (var literal in expressionLiteralArray)
                    {
                        context = literal.GetSpanContext();
                        builder.AddRange(literal.LiteralTokens);
                    }
                    var rewritten = SyntaxFactory.CSharpExpressionLiteral(builder.ToList()).Green.CreateRed(node.Parent, position);
                    rewritten = context != null?rewritten.WithSpanContext(context) : rewritten;

                    Visit(rewritten);
                }
                else
                {
                    Visit(node);
                }
            }
コード例 #8
0
    private SyntaxList <RazorSyntaxNode> GetLegacyChildren()
    {
        // This method returns the children of this start tag in legacy format.
        // This is needed to generate the same classified spans as the legacy syntax tree.
        var builder = new SyntaxListBuilder(5);
        var tokens  = SyntaxListBuilder <SyntaxToken> .Create();

        var context = this.GetSpanContext();

        // We want to know if this tag contains non-whitespace attribute content to set the appropriate AcceptedCharacters.
        // The prefix of a start tag(E.g '|<foo| attr>') will have 'Any' accepted characters if non-whitespace attribute content exists.
        var acceptsAnyContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault());

        acceptsAnyContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.Any;
        var containsAttributesContent = false;

        foreach (var attribute in Attributes)
        {
            if (!string.IsNullOrWhiteSpace(attribute.GetContent()))
            {
                containsAttributesContent = true;
                break;
            }
        }

        if (!OpenAngle.IsMissing)
        {
            tokens.Add(OpenAngle);
        }
        if (Bang != null)
        {
            builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(acceptsAnyContext));

            tokens.Add(Bang);
            var acceptsNoneContext = new SpanContext(context.ChunkGenerator, SpanEditHandler.CreateDefault());
            acceptsNoneContext.EditHandler.AcceptedCharacters = AcceptedCharactersInternal.None;
            builder.Add(SyntaxFactory.RazorMetaCode(tokens.Consume()).WithSpanContext(acceptsNoneContext));
        }
        if (!Name.IsMissing)
        {
            tokens.Add(Name);
        }

        builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(containsAttributesContent ? acceptsAnyContext : context));

        builder.AddRange(Attributes);

        if (ForwardSlash != null)
        {
            tokens.Add(ForwardSlash);
        }
        if (!CloseAngle.IsMissing)
        {
            tokens.Add(CloseAngle);
        }

        if (tokens.Count > 0)
        {
            builder.Add(SyntaxFactory.MarkupTextLiteral(tokens.Consume()).WithSpanContext(context));
        }

        return(new SyntaxList <RazorSyntaxNode>(builder.ToListNode().CreateRed(this, Position)));
    }
コード例 #9
0
        private static SyntaxList <RazorSyntaxNode> GetRewrittenChildren(
            string tagName,
            bool validStructure,
            MarkupStartTagSyntax tagBlock,
            TagHelperBinding bindingResult,
            RazorParserFeatureFlags featureFlags,
            ErrorSink errorSink,
            RazorSourceDocument source)
        {
            var tagHelperBuilder = SyntaxListBuilder <RazorSyntaxNode> .Create();

            var processedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase);

            if (tagBlock.Children.Count == 1)
            {
                // Tag with no attributes. We have nothing to rewrite here.
                return(tagBlock.Children);
            }

            // Add the tag start
            tagHelperBuilder.Add(tagBlock.Children.First());

            // We skip the first child "<tagname" and take everything up to the ending portion of the tag ">" or "/>".
            // If the tag does not have a valid structure then there's no close angle to ignore.
            var tokenOffset = validStructure ? 1 : 0;

            for (var i = 1; i < tagBlock.Children.Count - tokenOffset; i++)
            {
                var            isMinimized           = false;
                var            attributeNameLocation = SourceLocation.Undefined;
                var            child = tagBlock.Children[i];
                TryParseResult result;
                if (child is MarkupAttributeBlockSyntax attributeBlock)
                {
                    attributeNameLocation = attributeBlock.Name.GetSourceLocation(source);
                    result = TryParseAttribute(
                        tagName,
                        attributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    tagHelperBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock)
                {
                    isMinimized           = true;
                    attributeNameLocation = minimizedAttributeBlock.Name.GetSourceLocation(source);
                    result = TryParseMinimizedAttribute(
                        tagName,
                        minimizedAttributeBlock,
                        bindingResult.Descriptors,
                        errorSink,
                        processedBoundAttributeNames);
                    tagHelperBuilder.Add(result.RewrittenAttribute);
                }
                else if (child is MarkupMiscAttributeContentSyntax miscContent)
                {
                    foreach (var contentChild in miscContent.Children)
                    {
                        if (contentChild is CSharpCodeBlockSyntax codeBlock)
                        {
                            // TODO: Accept more than just Markup attributes: https://github.com/aspnet/Razor/issues/96.
                            // Something like:
                            // <input @checked />
                            var location   = new SourceSpan(codeBlock.GetSourceLocation(source), codeBlock.FullWidth);
                            var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelpersCannotHaveCSharpInTagDeclaration(location, tagName);
                            errorSink.OnError(diagnostic);
                            break;
                        }
                        else
                        {
                            // If the original span content was whitespace it ultimately means the tag
                            // that owns this "attribute" is malformed and is expecting a user to type a new attribute.
                            // ex: <myTH class="btn"| |
                            var literalContent = contentChild.GetContent();
                            if (!string.IsNullOrWhiteSpace(literalContent))
                            {
                                var location   = contentChild.GetSourceSpan(source);
                                var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelperAttributeListMustBeWellFormed(location);
                                errorSink.OnError(diagnostic);
                                break;
                            }
                        }
                    }

                    result = null;
                }
                else
                {
                    result = null;
                }

                // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span.
                if (result == null)
                {
                    // Error occurred while parsing the attribute. Don't try parsing the rest to avoid misleading errors.
                    for (var j = i; j < tagBlock.Children.Count; j++)
                    {
                        tagHelperBuilder.Add(tagBlock.Children[j]);
                    }

                    return(tagHelperBuilder.ToList());
                }

                // Check if it's a non-boolean bound attribute that is minimized or if it's a bound
                // non-string attribute that has null or whitespace content.
                var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute;
                if ((isMinimized &&
                     result.IsBoundAttribute &&
                     !isValidMinimizedAttribute) ||
                    (!isMinimized &&
                     result.IsBoundNonStringAttribute &&
                     string.IsNullOrWhiteSpace(GetAttributeValueContent(result.RewrittenAttribute))))
                {
                    var errorLocation    = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var propertyTypeName = GetPropertyType(result.AttributeName, bindingResult.Descriptors);
                    var diagnostic       = RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(errorLocation, result.AttributeName, tagName, propertyTypeName);
                    errorSink.OnError(diagnostic);
                }

                // Check if the attribute was a prefix match for a tag helper dictionary property but the
                // dictionary key would be the empty string.
                if (result.IsMissingDictionaryKey)
                {
                    var errorLocation = new SourceSpan(attributeNameLocation, result.AttributeName.Length);
                    var diagnostic    = RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(errorLocation, result.AttributeName, tagName);
                    errorSink.OnError(diagnostic);
                }
            }

            if (validStructure)
            {
                // Add the tag end.
                tagHelperBuilder.Add(tagBlock.Children[tagBlock.Children.Count - 1]);
            }

            return(tagHelperBuilder.ToList());
        }