示例#1
0
            private void ValidateParentAllowsContent(SyntaxNode child)
            {
                if (HasAllowedChildren())
                {
                    var isDisallowedContent = true;
                    if (_featureFlags.AllowHtmlCommentsInTagHelpers)
                    {
                        isDisallowedContent = !IsComment(child) &&
                                              !child.IsTransitionSpanKind() &&
                                              !child.IsCodeSpanKind();
                    }

                    if (isDisallowedContent)
                    {
                        var content = child.GetContent();
                        if (!string.IsNullOrWhiteSpace(content))
                        {
                            var trimmedStart          = content.TrimStart();
                            var whitespace            = content.Substring(0, content.Length - trimmedStart.Length);
                            var errorStart            = SourceLocationTracker.Advance(child.GetSourceLocation(_source), whitespace);
                            var length                = trimmedStart.TrimEnd().Length;
                            var allowedChildren       = CurrentTagHelperTracker.AllowedChildren;
                            var allowedChildrenString = string.Join(", ", allowedChildren);
                            _errorSink.OnError(
                                RazorDiagnosticFactory.CreateTagHelper_CannotHaveNonTagContent(
                                    new SourceSpan(errorStart, length),
                                    CurrentTagHelperTracker.TagName,
                                    allowedChildrenString));
                        }
                    }
                }
            }
示例#2
0
        public void SymbolBoundAttributes_Whitespace(string attributeName)
        {
            // Arrange
            var attributeNameLength = attributeName.Length;
            var newlineLength       = Environment.NewLine.Length;
            var prefixLocation1     = new SourceLocation(
                absoluteIndex: 2,
                lineIndex: 0,
                characterIndex: 2);
            var suffixLocation1 = new SourceLocation(
                absoluteIndex: 10 + newlineLength + attributeNameLength,
                lineIndex: 1,
                characterIndex: 7 + attributeNameLength);
            var valueLocation1 = new SourceLocation(
                absoluteIndex: 7 + attributeNameLength + newlineLength,
                lineIndex: 1,
                characterIndex: 4 + attributeNameLength);
            var prefixLocation2 = SourceLocationTracker.Advance(suffixLocation1, "'");
            var suffixLocation2 = new SourceLocation(
                absoluteIndex: 17 + attributeNameLength * 2 + newlineLength * 2,
                lineIndex: 2,
                characterIndex: 5 + attributeNameLength);
            var valueLocation2 = new SourceLocation(
                absoluteIndex: 14 + attributeNameLength * 2 + newlineLength * 2,
                lineIndex: 2,
                characterIndex: 2 + attributeNameLength);

            // Act & Assert
            ParseBlockTest(
                $"<a {Environment.NewLine}  {attributeName}='Foo'\t{Environment.NewLine}{attributeName}='Bar' />",
                new MarkupBlock(
                    new MarkupTagBlock(
                        Factory.Markup("<a"),
                        new MarkupBlock(
                            new AttributeBlockChunkGenerator(
                                attributeName,
                                prefix: new LocationTagged <string>(
                                    $" {Environment.NewLine}  {attributeName}='", prefixLocation1),
                                suffix: new LocationTagged <string>("'", suffixLocation1)),
                            Factory.Markup($" {Environment.NewLine}  {attributeName}='").With(SpanChunkGenerator.Null),
                            Factory.Markup("Foo").With(
                                new LiteralAttributeChunkGenerator(
                                    prefix: new LocationTagged <string>(string.Empty, valueLocation1),
                                    value: new LocationTagged <string>("Foo", valueLocation1))),
                            Factory.Markup("'").With(SpanChunkGenerator.Null)),
                        new MarkupBlock(
                            new AttributeBlockChunkGenerator(
                                attributeName,
                                prefix: new LocationTagged <string>(
                                    $"\t{Environment.NewLine}{attributeName}='", prefixLocation2),
                                suffix: new LocationTagged <string>("'", suffixLocation2)),
                            Factory.Markup($"\t{Environment.NewLine}{attributeName}='").With(SpanChunkGenerator.Null),
                            Factory.Markup("Bar").With(
                                new LiteralAttributeChunkGenerator(
                                    prefix: new LocationTagged <string>(string.Empty, valueLocation2),
                                    value: new LocationTagged <string>("Bar", valueLocation2))),
                            Factory.Markup("'").With(SpanChunkGenerator.Null)),
                        Factory.Markup(" />").Accepts(AcceptedCharactersInternal.None))));
        }
示例#3
0
        public void UpdateLocationAdvancesCorrectlyForMultiLineString()
        {
            // Arrange
            var location = TestStartLocation;

            // Act
            var currentLocation = SourceLocationTracker.Advance(location, "foo\nbar\rbaz\r\nbox");

            // Assert
            Assert.Equal(26, currentLocation.AbsoluteIndex);
            Assert.Equal(45, currentLocation.LineIndex);
            Assert.Equal(3, currentLocation.CharacterIndex);
        }
示例#4
0
        public void Advance_PreservesSourceLocationFilePath(string path)
        {
            // Arrange
            var sourceLocation = new SourceLocation(path, 15, 2, 8);

            // Act
            var result = SourceLocationTracker.Advance(sourceLocation, "Hello world");

            // Assert
            Assert.Equal(path, result.FilePath);
            Assert.Equal(26, result.AbsoluteIndex);
            Assert.Equal(2, result.LineIndex);
            Assert.Equal(19, result.CharacterIndex);
        }
 private void ValidateParentAllowsContent(Span child, ErrorSink errorSink)
 {
     if (HasAllowedChildren())
     {
         var content = child.Content;
         if (!string.IsNullOrWhiteSpace(content))
         {
             var trimmedStart          = content.TrimStart();
             var whitespace            = content.Substring(0, content.Length - trimmedStart.Length);
             var errorStart            = SourceLocationTracker.Advance(child.Start, whitespace);
             var length                = trimmedStart.TrimEnd().Length;
             var allowedChildren       = _currentTagHelperTracker.AllowedChildren;
             var allowedChildrenString = string.Join(", ", allowedChildren);
             errorSink.OnError(
                 RazorDiagnosticFactory.CreateTagHelper_CannotHaveNonTagContent(
                     new SourceSpan(errorStart, length),
                     _currentTagHelperTracker.TagName,
                     allowedChildrenString));
         }
     }
 }
示例#6
0
 private void ValidateParentAllowsContent(Span child, ErrorSink errorSink)
 {
     if (HasAllowedChildren())
     {
         var content = child.Content;
         if (!string.IsNullOrWhiteSpace(content))
         {
             var trimmedStart          = content.TrimStart();
             var whitespace            = content.Substring(0, content.Length - trimmedStart.Length);
             var errorStart            = SourceLocationTracker.Advance(child.Start, whitespace);
             var length                = trimmedStart.TrimEnd().Length;
             var allowedChildren       = _currentTagHelperTracker.AllowedChildren;
             var allowedChildrenString = string.Join(", ", allowedChildren);
             errorSink.OnError(
                 errorStart,
                 LegacyResources.FormatTagHelperParseTreeRewriter_CannotHaveNonTagContent(
                     _currentTagHelperTracker.TagName,
                     allowedChildrenString),
                 length);
         }
     }
 }
        private void BuildMalformedTagHelpers(int count, ErrorSink errorSink)
        {
            for (var i = 0; i < count; i++)
            {
                var tracker = _trackerStack.Peek();

                // Skip all non-TagHelper entries. Non TagHelper trackers do not need to represent well-formed HTML.
                if (!tracker.IsTagHelper)
                {
                    PopTrackerStack();
                    continue;
                }

                var malformedTagHelper = ((TagHelperBlockTracker)tracker).Builder;

                errorSink.OnError(
                    RazorDiagnosticFactory.CreateParsing_TagHelperFoundMalformedTagHelper(
                        new SourceSpan(SourceLocationTracker.Advance(malformedTagHelper.Start, "<"), malformedTagHelper.TagName.Length),
                        malformedTagHelper.TagName));

                BuildCurrentlyTrackedTagHelperBlock(endTag: null);
            }
        }
示例#8
0
        private void BuildMalformedTagHelpers(int count, ErrorSink errorSink)
        {
            for (var i = 0; i < count; i++)
            {
                var tracker = _trackerStack.Peek();

                // Skip all non-TagHelper entries. Non TagHelper trackers do not need to represent well-formed HTML.
                if (!tracker.IsTagHelper)
                {
                    PopTrackerStack();
                    continue;
                }

                var malformedTagHelper = ((TagHelperBlockTracker)tracker).Builder;

                errorSink.OnError(
                    SourceLocationTracker.Advance(malformedTagHelper.Start, "<"),
                    LegacyResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
                        malformedTagHelper.TagName),
                    malformedTagHelper.TagName.Length);

                BuildCurrentlyTrackedTagHelperBlock(endTag: null);
            }
        }
示例#9
0
        // This method handles cases when the attribute is a simple span attribute such as
        // class="something moresomething".  This does not handle complex attributes such as
        // class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
        private static TryParseResult TryParseSpan(
            Span span,
            IEnumerable <TagHelperDescriptor> descriptors,
            ErrorSink errorSink,
            HashSet <string> processedBoundAttributeNames)
        {
            var afterEquals = false;
            var builder     = new SpanBuilder(span.Start)
            {
                ChunkGenerator = span.ChunkGenerator,
                EditHandler    = span.EditHandler,
                Kind           = span.Kind
            };

            // Will contain symbols that represent a single attribute value: <input| class="btn"| />
            var htmlSymbols = span.Symbols.OfType <HtmlSymbol>().ToArray();
            var capturedAttributeValueStart = false;
            var attributeValueStartLocation = span.Start;

            // Default to DoubleQuotes. We purposefully do not persist NoQuotes ValueStyle to stay consistent with the
            // TryParseBlock() variation of attribute parsing.
            var attributeValueStyle = AttributeStructure.DoubleQuotes;

            // The symbolOffset is initialized to 0 to expect worst case: "class=". If a quote is found later on for
            // the attribute value the symbolOffset is adjusted accordingly.
            var    symbolOffset = 0;
            string name         = null;

            // Iterate down through the symbols to find the name and the start of the value.
            // We subtract the symbolOffset so we don't accept an ending quote of a span.
            for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++)
            {
                var symbol = htmlSymbols[i];

                if (afterEquals)
                {
                    // We've captured all leading whitespace, the attribute name, and an equals with an optional
                    // quote/double quote. We're now at: " asp-for='|...'" or " asp-for=|..."
                    // The goal here is to capture all symbols until the end of the attribute. Note this will not
                    // consume an ending quote due to the symbolOffset.

                    // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the
                    // parent which is why we need to mark our start location prior to adding the symbol.
                    // This is needed to know the location of the attribute value start within the document.
                    if (!capturedAttributeValueStart)
                    {
                        capturedAttributeValueStart = true;

                        attributeValueStartLocation = symbol.Start;
                    }

                    builder.Accept(symbol);
                }
                else if (name == null && HtmlMarkupParser.IsValidAttributeNameSymbol(symbol))
                {
                    // We've captured all leading whitespace prior to the attribute name.
                    // We're now at: " |asp-for='...'" or " |asp-for=..."
                    // The goal here is to capture the attribute name.

                    var nameBuilder = new StringBuilder();
                    // Move the indexer past the attribute name symbols.
                    for (var j = i; j < htmlSymbols.Length; j++)
                    {
                        var nameSymbol = htmlSymbols[j];
                        if (!HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol))
                        {
                            break;
                        }

                        nameBuilder.Append(nameSymbol.Content);
                        i++;
                    }

                    i--;

                    name = nameBuilder.ToString();
                    attributeValueStartLocation = SourceLocationTracker.Advance(attributeValueStartLocation, name);
                }
                else if (symbol.Type == HtmlSymbolType.Equals)
                {
                    // We've captured all leading whitespace and the attribute name.
                    // We're now at: " asp-for|='...'" or " asp-for|=..."
                    // The goal here is to consume the equal sign and the optional single/double-quote.

                    // The coming symbols will either be a quote or value (in the case that the value is unquoted).

                    SourceLocation symbolStartLocation;

                    // Skip the whitespace preceding the start of the attribute value.
                    do
                    {
                        i++; // Start from the symbol after '='.
                    } while (i < htmlSymbols.Length &&
                             (htmlSymbols[i].Type == HtmlSymbolType.WhiteSpace ||
                              htmlSymbols[i].Type == HtmlSymbolType.NewLine));

                    // Check for attribute start values, aka single or double quote
                    if (i < htmlSymbols.Length && IsQuote(htmlSymbols[i]))
                    {
                        if (htmlSymbols[i].Type == HtmlSymbolType.SingleQuote)
                        {
                            attributeValueStyle = AttributeStructure.SingleQuotes;
                        }

                        symbolStartLocation = htmlSymbols[i].Start;

                        // If there's a start quote then there must be an end quote to be valid, skip it.
                        symbolOffset = 1;
                    }
                    else
                    {
                        // We are at the symbol after equals. Go back to equals to ensure we don't skip past that symbol.
                        i--;

                        symbolStartLocation = symbol.Start;
                    }

                    attributeValueStartLocation = new SourceLocation(
                        symbolStartLocation.FilePath,
                        symbolStartLocation.AbsoluteIndex + 1,
                        symbolStartLocation.LineIndex,
                        symbolStartLocation.CharacterIndex + 1);

                    afterEquals = true;
                }
                else if (symbol.Type == HtmlSymbolType.WhiteSpace)
                {
                    // We're at the start of the attribute, this branch may be hit on the first iterations of
                    // the loop since the parser separates attributes with their spaces included as symbols.
                    // We're at: "| asp-for='...'" or "| asp-for=..."
                    // Note: This will not be hit even for situations like asp-for  ="..." because the core Razor
                    // parser currently does not know how to handle attributes in that format. This will be addressed
                    // by https://github.com/aspnet/Razor/issues/123.

                    attributeValueStartLocation = SourceLocationTracker.Advance(attributeValueStartLocation, symbol.Content);
                }
            }

            // After all symbols have been added we need to set the builders start position so we do not indirectly
            // modify the span's start location.
            builder.Start = attributeValueStartLocation;

            if (name == null)
            {
                // We couldn't find a name, 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"| |
                if (!string.IsNullOrWhiteSpace(span.Content))
                {
                    errorSink.OnError(
                        span.Start,
                        LegacyResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed,
                        span.Content.Length);
                }

                return(null);
            }

            var result = CreateTryParseResult(name, descriptors, processedBoundAttributeNames);

            // If we're not after an equal then we should treat the value as if it were a minimized attribute.
            Span attributeValue = null;

            if (afterEquals)
            {
                attributeValue = CreateMarkupAttribute(builder, result);
            }
            else
            {
                attributeValueStyle = AttributeStructure.Minimized;
            }

            result.AttributeValueNode = attributeValue;
            result.AttributeStructure = attributeValueStyle;
            return(result);
        }
示例#10
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.GetTagNameWithOptionalBang();
                    if (TryRewriteTagHelperStart(startTag, node.EndTag, 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 && node.EndTag == null)
                            {
                                return(rewrittenTagHelper);
                            }

                            // This tag contains a body and/or an end tag 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: node.EndTag));
                        }
                        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 (node.EndTag != null || (!startTag.IsSelfClosing() && !startTag.IsVoidElement()))
                        {
                            // Ideally we don't want to keep track of self-closing or void tags.
                            // But if a matching end tag exists, keep track of the start tag no matter what.
                            // We will just assume the parser had a good reason to do this.
                            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.GetTagNameWithOptionalBang();
                    if (TryRewriteTagHelperEnd(startTag, 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));
            }
示例#11
0
 private static SourceLocation GetEndTagDeclarationErrorStart(MarkupEndTagSyntax tagBlock, RazorSourceDocument source)
 {
     return(SourceLocationTracker.Advance(tagBlock.GetSourceLocation(source), "</"));
 }
示例#12
0
            private bool TryRewriteTagHelperEnd(MarkupStartTagSyntax startTag, MarkupEndTagSyntax endTag, out MarkupTagHelperEndTagSyntax rewritten)
            {
                rewritten = null;
                var tagName = endTag.GetTagNameWithOptionalBang();

                // Could not determine tag name, it can't be a TagHelper, continue on and track the element.
                if (string.IsNullOrEmpty(tagName) || tagName.StartsWith("!", StringComparison.Ordinal))
                {
                    return(false);
                }

                var tracker      = CurrentTagHelperTracker;
                var tagNameScope = tracker?.TagName ?? string.Empty;

                if (!IsPotentialTagHelperEnd(tagName, endTag))
                {
                    return(false);
                }

                // Validate that our end tag matches the currently scoped tag, if not we may need to error.
                if (startTag != null && tagNameScope.Equals(tagName, StringComparison.OrdinalIgnoreCase))
                {
                    // If there are additional end tags required before we can build our block it means we're in a
                    // situation like this: <myth req="..."><myth></myth></myth> where we're at the inside </myth>.
                    if (tracker.OpenMatchingTags > 0)
                    {
                        tracker.OpenMatchingTags--;

                        return(false);
                    }

                    ValidateEndTagSyntax(tagName, endTag);

                    _trackerStack.Pop();
                }
                else
                {
                    var tagHelperBinding = _tagHelperBinder.GetBinding(
                        tagName,
                        attributes: Array.Empty <KeyValuePair <string, string> >(),
                        parentTagName: CurrentParentTagName,
                        parentIsTagHelper: CurrentParentIsTagHelper);

                    // If there are not TagHelperDescriptors associated with the end tag block that also have no
                    // required attributes then it means we can't be a TagHelper, bail out.
                    if (tagHelperBinding == null)
                    {
                        return(false);
                    }

                    foreach (var descriptor in tagHelperBinding.Descriptors)
                    {
                        var boundRules  = tagHelperBinding.Mappings[descriptor];
                        var invalidRule = boundRules.FirstOrDefault(rule => rule.TagStructure == TagStructure.WithoutEndTag);

                        if (invalidRule != null)
                        {
                            // End tag TagHelper that states it shouldn't have an end tag.
                            _errorSink.OnError(
                                RazorDiagnosticFactory.CreateParsing_TagHelperMustNotHaveAnEndTag(
                                    new SourceSpan(SourceLocationTracker.Advance(endTag.GetSourceLocation(_source), "</"), tagName.Length),
                                    tagName,
                                    descriptor.DisplayName,
                                    invalidRule.TagStructure));

                            return(false);
                        }
                    }
                }

                rewritten = SyntaxFactory.MarkupTagHelperEndTag(
                    endTag.OpenAngle, endTag.ForwardSlash, endTag.Bang, endTag.Name, endTag.MiscAttributeContent, endTag.CloseAngle);

                return(true);
            }
        private static SourceLocation GetTagDeclarationErrorStart(Block tagBlock)
        {
            var advanceBy = IsEndTag(tagBlock) ? "</" : "<";

            return(SourceLocationTracker.Advance(tagBlock.Start, advanceBy));
        }
        private bool TryRewriteTagHelper(Block tagBlock, ErrorSink errorSink)
        {
            // Get tag name of the current block (doesn't matter if it's an end or start tag)
            var tagName = GetTagName(tagBlock);

            // Could not determine tag name, it can't be a TagHelper, continue on and track the element.
            if (tagName == null)
            {
                return(false);
            }

            TagHelperBinding tagHelperBinding;

            if (!IsPotentialTagHelper(tagName, tagBlock))
            {
                return(false);
            }

            var tracker      = _currentTagHelperTracker;
            var tagNameScope = tracker?.TagName ?? string.Empty;

            if (!IsEndTag(tagBlock))
            {
                // We're now in a start tag block, we first need to see if the tag block is a tag helper.
                var elementAttributes = GetAttributeNameValuePairs(tagBlock);

                tagHelperBinding = _tagHelperBinder.GetBinding(
                    tagName,
                    elementAttributes,
                    CurrentParentTagName,
                    CurrentParentIsTagHelper);

                // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper
                if (tagHelperBinding == null)
                {
                    // If the current tag matches the current TagHelper scope it means the parent TagHelper matched
                    // all the required attributes but the current one did not; therefore, we need to increment the
                    // OpenMatchingTags counter for current the TagHelperBlock so we don't end it too early.
                    // ex: <myth req="..."><myth></myth></myth> We don't want the first myth to close on the inside
                    // tag.
                    if (string.Equals(tagNameScope, tagName, StringComparison.OrdinalIgnoreCase))
                    {
                        tracker.OpenMatchingTags++;
                    }

                    return(false);
                }

                ValidateParentAllowsTagHelper(tagName, tagBlock, errorSink);
                ValidateBinding(tagHelperBinding, tagName, tagBlock, errorSink);

                // We're in a start TagHelper block.
                var validTagStructure = ValidateTagSyntax(tagName, tagBlock, errorSink);

                var builder = TagHelperBlockRewriter.Rewrite(
                    tagName,
                    validTagStructure,
                    _featureFlags,
                    tagBlock,
                    tagHelperBinding,
                    errorSink);

                // Track the original start tag so the editor knows where each piece of the TagHelperBlock lies
                // for formatting.
                builder.SourceStartTag = tagBlock;

                // Found a new tag helper block
                TrackTagHelperBlock(builder);

                // If it's a non-content expecting block then we don't have to worry about nested children within the
                // tag. Complete it.
                if (builder.TagMode == TagMode.SelfClosing || builder.TagMode == TagMode.StartTagOnly)
                {
                    BuildCurrentlyTrackedTagHelperBlock(endTag: null);
                }
            }
            else
            {
                // Validate that our end tag matches the currently scoped tag, if not we may need to error.
                if (tagNameScope.Equals(tagName, StringComparison.OrdinalIgnoreCase))
                {
                    // If there are additional end tags required before we can build our block it means we're in a
                    // situation like this: <myth req="..."><myth></myth></myth> where we're at the inside </myth>.
                    if (tracker.OpenMatchingTags > 0)
                    {
                        tracker.OpenMatchingTags--;

                        return(false);
                    }

                    ValidateTagSyntax(tagName, tagBlock, errorSink);

                    BuildCurrentlyTrackedTagHelperBlock(tagBlock);
                }
                else
                {
                    tagHelperBinding = _tagHelperBinder.GetBinding(
                        tagName,
                        attributes: Array.Empty <KeyValuePair <string, string> >(),
                        parentTagName: CurrentParentTagName,
                        parentIsTagHelper: CurrentParentIsTagHelper);

                    // If there are not TagHelperDescriptors associated with the end tag block that also have no
                    // required attributes then it means we can't be a TagHelper, bail out.
                    if (tagHelperBinding == null)
                    {
                        return(false);
                    }

                    foreach (var descriptor in tagHelperBinding.Descriptors)
                    {
                        var boundRules  = tagHelperBinding.GetBoundRules(descriptor);
                        var invalidRule = boundRules.FirstOrDefault(rule => rule.TagStructure == TagStructure.WithoutEndTag);

                        if (invalidRule != null)
                        {
                            // End tag TagHelper that states it shouldn't have an end tag.
                            errorSink.OnError(
                                RazorDiagnosticFactory.CreateParsing_TagHelperMustNotHaveAnEndTag(
                                    new SourceSpan(SourceLocationTracker.Advance(tagBlock.Start, "</"), tagName.Length),
                                    tagName,
                                    descriptor.DisplayName,
                                    invalidRule.TagStructure));

                            return(false);
                        }
                    }

                    // Current tag helper scope does not match the end tag. Attempt to recover the tag
                    // helper by looking up the previous tag helper scopes for a matching tag. If we
                    // can't recover it means there was no corresponding tag helper start tag.
                    if (TryRecoverTagHelper(tagName, tagBlock, errorSink))
                    {
                        ValidateParentAllowsTagHelper(tagName, tagBlock, errorSink);
                        ValidateTagSyntax(tagName, tagBlock, errorSink);

                        // Successfully recovered, move onto the next element.
                    }
                    else
                    {
                        // Could not recover, the end tag helper has no corresponding start tag, create
                        // an error based on the current childBlock.
                        errorSink.OnError(
                            RazorDiagnosticFactory.CreateParsing_TagHelperFoundMalformedTagHelper(
                                new SourceSpan(SourceLocationTracker.Advance(tagBlock.Start, "</"), tagName.Length), tagName));

                        return(false);
                    }
                }
            }

            return(true);
        }
示例#15
0
            private static SourceLocation GetTagDeclarationErrorStart(MarkupTagBlockSyntax tagBlock, RazorSourceDocument source)
            {
                var advanceBy = IsEndTag(tagBlock) ? "</" : "<";

                return(SourceLocationTracker.Advance(tagBlock.GetSourceLocation(source), advanceBy));
            }