public void GetAttributeNameValuePairs_ParsesPairsCorrectly(
            string documentContent,
            IEnumerable<KeyValuePair<string, string>> expectedPairs)
        {
            // Arrange
            var errorSink = new ErrorSink();
            var parseResult = ParseDocument(documentContent, errorSink);
            var document = parseResult.Document;
            var rewriters = RazorParser.GetDefaultRewriters(new HtmlMarkupParser());
            var rewritingContext = new RewritingContext(document, errorSink);
            foreach (var rewriter in rewriters)
            {
                rewriter.Rewrite(rewritingContext);
            }
            var block = rewritingContext.SyntaxTree.Children.First();
            var parseTreeRewriter = new TagHelperParseTreeRewriter(provider: null);

            // Assert - Guard
            var tagBlock = Assert.IsType<Block>(block);
            Assert.Equal(BlockType.Tag, tagBlock.Type);
            Assert.Empty(errorSink.Errors);

            // Act
            var pairs = parseTreeRewriter.GetAttributeNameValuePairs(tagBlock);

            // Assert
            Assert.Equal(expectedPairs, pairs);
        }
        private bool TryRecoverTagHelper(string tagName, RewritingContext context)
        {
            var malformedTagHelperCount = 0;

            foreach (var tag in _tagStack)
            {
                if (tag.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
                {
                    break;
                }

                malformedTagHelperCount++;
            }

            // If the malformedTagHelperCount == _tagStack.Count it means we couldn't find a begin tag for the tag
            // helper, can't recover.
            if (malformedTagHelperCount != _tagStack.Count)
            {
                BuildMalformedTagHelpers(malformedTagHelperCount, context);

                // One final build, this is the build that completes our target tag helper block which is not malformed.
                BuildCurrentlyTrackedTagHelperBlock();

                // We were able to recover
                return(true);
            }

            // Could not recover tag helper. Aka we found a tag helper end tag without a corresponding begin tag.
            return(false);
        }
        private static bool ValidTagStructure(string tagName, Block tag, RewritingContext context)
        {
            // We assume an invalid structure until we verify that the tag meets all of our "valid structure" criteria.
            var invalidStructure = true;

            // No need to validate the tag end because in order to be a tag block it must start with '<'.
            var tagEnd = tag.Children.Last() as Span;

            // If our tag end is not a markup span it means it's some sort of code SyntaxTreeNode (not a valid format)
            if (tagEnd != null && tagEnd.Kind == SpanKind.Markup)
            {
                var endSymbol = tagEnd.Symbols.LastOrDefault() as HtmlSymbol;

                if (endSymbol != null && endSymbol.Type == HtmlSymbolType.CloseAngle)
                {
                    invalidStructure = false;
                }
            }

            if (invalidStructure)
            {
                context.ErrorSink.OnError(
                    tag.Start,
                    RazorResources.FormatTagHelpersParseTreeRewriter_MissingCloseAngle(tagName));
            }

            return(!invalidStructure);
        }
Exemple #4
0
        private void ValidateRewrittenSyntaxTree(RewritingContext context)
        {
            // If the blockStack still has elements in it that means there's at least one malformed TagHelper block in
            // the document, that's the only way we can have a non-zero _blockStack.Count.
            if (_blockStack.Count != 0)
            {
                // We reverse the children so we can search from the back to the front for the TagHelper that is
                // malformed.
                var candidateChildren  = _currentBlock.Children.Reverse();
                var malformedTagHelper = candidateChildren.OfType <TagHelperBlock>().FirstOrDefault();

                // If the malformed tag helper is null that means something other than a TagHelper caused the
                // unbalancing of the syntax tree (should never happen).
                Debug.Assert(malformedTagHelper != null);

                // We only create a single error because we can't reasonably determine other invalid tag helpers in the
                // document; having one malformed tag helper puts the document into an invalid state.
                context.ErrorSink.OnError(
                    malformedTagHelper.Start,
                    RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
                        malformedTagHelper.TagName));

                // We need to build the remaining blocks in the stack to ensure we don't return an invalid syntax tree.
                do
                {
                    BuildCurrentlyTrackedTagHelperBlock();
                }while (_blockStack.Count != 0);
            }
        }
        public void Rewrite_Moves_Whitespace_Preceeding_ExpressionBlock_To_Parent_Block()
        {
            // Arrange
            var factory = SpanFactory.CreateCsHtml();
            var start   = new MarkupBlock(
                factory.Markup("test"),
                new ExpressionBlock(
                    factory.Code("    ").AsExpression(),
                    factory.CodeTransition(SyntaxConstants.TransitionString),
                    factory.Code("foo").AsExpression()
                    ),
                factory.Markup("test")
                );
            var rewriter         = new WhiteSpaceRewriter(new HtmlMarkupParser().BuildSpan);
            var rewritingContext = new RewritingContext(start, new ErrorSink());

            // Act
            rewriter.Rewrite(rewritingContext);

            factory.Reset();

            // Assert
            ParserTestBase.EvaluateParseTree(rewritingContext.SyntaxTree, new MarkupBlock(
                                                 factory.Markup("test"),
                                                 factory.Markup("    "),
                                                 new ExpressionBlock(
                                                     factory.CodeTransition(SyntaxConstants.TransitionString),
                                                     factory.Code("foo").AsExpression()
                                                     ),
                                                 factory.Markup("test")
                                                 ));
        }
Exemple #6
0
        public void ConditionalAttributeCollapserDoesNotRemoveUrlAttributeValues()
        {
            // Act
            var results          = ParseDocument("<a href='~/Foo/Bar' />");
            var rewritingContext = new RewritingContext(results.Document);

            new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritingContext);
            new MarkupCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritingContext);
            var rewritten = rewritingContext.SyntaxTree;

            // Assert
            Assert.Equal(0, results.ParserErrors.Count);
            EvaluateParseTree(rewritten,
                              new MarkupBlock(
                                  new MarkupTagBlock(
                                      Factory.Markup("<a"),
                                      new MarkupBlock(new AttributeBlockCodeGenerator(name: "href", prefix: new LocationTagged <string>(" href='", 2, 0, 2), suffix: new LocationTagged <string>("'", 18, 0, 18)),
                                                      Factory.Markup(" href='").With(SpanCodeGenerator.Null),
                                                      Factory.Markup("~/Foo/Bar")
                                                      .WithEditorHints(EditorHints.VirtualPath)
                                                      .With(new LiteralAttributeCodeGenerator(
                                                                new LocationTagged <string>(string.Empty, 9, 0, 9),
                                                                new LocationTagged <SpanCodeGenerator>(new ResolveUrlCodeGenerator(), 9, 0, 9))),
                                                      Factory.Markup("'").With(SpanCodeGenerator.Null)),
                                      Factory.Markup(" />"))));
        }
Exemple #7
0
        public void Rewrite(RewritingContext context)
        {
            RewriteTags(context.SyntaxTree);

            ValidateRewrittenSyntaxTree(context);

            context.SyntaxTree = _currentBlock.Build();
        }
        private void RewriteTags(Block input, RewritingContext context)
        {
            // We want to start a new block without the children from existing (we rebuild them).
            TrackBlock(new BlockBuilder
            {
                Type          = input.Type,
                CodeGenerator = input.CodeGenerator
            });

            var activeTagHelpers = _tagStack.Count;

            foreach (var child in input.Children)
            {
                if (child.IsBlock)
                {
                    var childBlock = (Block)child;

                    if (childBlock.Type == BlockType.Tag)
                    {
                        if (TryRewriteTagHelper(childBlock, context))
                        {
                            continue;
                        }

                        // If we get to here it means that we're a normal html tag.  No need to iterate any deeper into
                        // the children of it because they wont be tag helpers.
                    }
                    else
                    {
                        // We're not an Html tag so iterate through children recursively.
                        RewriteTags(childBlock, context);
                        continue;
                    }
                }

                // At this point the child is a Span or Block with Type BlockType.Tag that doesn't happen to be a
                // tag helper.

                // Add the child to current block.
                _currentBlock.Children.Add(child);
            }

            // We captured the number of active tag helpers at the start of our logic, it should be the same. If not
            // it means that there are malformed tag helpers at the top of our stack.
            if (activeTagHelpers != _tagStack.Count)
            {
                // Malformed tag helpers built here will be tag helpers that do not have end tags in the current block
                // scope. Block scopes are special cases in Razor such as @<p> would cause an error because there's no
                // matching end </p> tag in the template block scope and therefore doesn't make sense as a tag helper.
                BuildMalformedTagHelpers(_tagStack.Count - activeTagHelpers, context);

                Debug.Assert(activeTagHelpers == _tagStack.Count);
            }

            BuildCurrentlyTrackedBlock();
        }
        private void RewriteTags(Block input, RewritingContext context)
        {
            // We want to start a new block without the children from existing (we rebuild them).
            TrackBlock(new BlockBuilder
            {
                Type = input.Type,
                CodeGenerator = input.CodeGenerator
            });

            var activeTagHelpers = _trackerStack.Count;

            foreach (var child in input.Children)
            {
                if (child.IsBlock)
                {
                    var childBlock = (Block)child;

                    if (childBlock.Type == BlockType.Tag)
                    {
                        if (TryRewriteTagHelper(childBlock, context))
                        {
                            continue;
                        }

                        // If we get to here it means that we're a normal html tag.  No need to iterate any deeper into
                        // the children of it because they wont be tag helpers.
                    }
                    else
                    {
                        // We're not an Html tag so iterate through children recursively.
                        RewriteTags(childBlock, context);
                        continue;
                    }
                }

                // At this point the child is a Span or Block with Type BlockType.Tag that doesn't happen to be a
                // tag helper.

                // Add the child to current block. 
                _currentBlock.Children.Add(child);
            }

            // We captured the number of active tag helpers at the start of our logic, it should be the same. If not
            // it means that there are malformed tag helpers at the top of our stack.
            if (activeTagHelpers != _trackerStack.Count)
            {
                // Malformed tag helpers built here will be tag helpers that do not have end tags in the current block 
                // scope. Block scopes are special cases in Razor such as @<p> would cause an error because there's no
                // matching end </p> tag in the template block scope and therefore doesn't make sense as a tag helper.
                BuildMalformedTagHelpers(_trackerStack.Count - activeTagHelpers, context);

                Debug.Assert(activeTagHelpers == _trackerStack.Count);
            }

            BuildCurrentlyTrackedBlock();
        }
        private void BuildMalformedTagHelpers(int count, RewritingContext context)
        {
            for (var i = 0; i < count; i++)
            {
                var malformedTagHelper = _tagStack.Peek();

                context.ErrorSink.OnError(
                    malformedTagHelper.Start,
                    RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
                        malformedTagHelper.TagName));

                BuildCurrentlyTrackedTagHelperBlock();
            }
        }
        private static bool ValidateTagSyntax(string tagName, Block tag, RewritingContext context)
        {
            // We assume an invalid syntax until we verify that the tag meets all of our "valid syntax" criteria.
            if (IsPartialTag(tag))
            {
                context.ErrorSink.OnError(
                    tag.Start,
                    RazorResources.FormatTagHelpersParseTreeRewriter_MissingCloseAngle(tagName));

                return(false);
            }

            return(true);
        }
        public void EvaluateData(
            TagHelperDescriptorProvider provider,
            string documentContent,
            MarkupBlock expectedOutput,
            IEnumerable<RazorError> expectedErrors)
        {
            var errorSink = new ErrorSink();
            var results = ParseDocument(documentContent, errorSink);
            var rewritingContext = new RewritingContext(results.Document, errorSink);
            new TagHelperParseTreeRewriter(provider).Rewrite(rewritingContext);
            var rewritten = rewritingContext.SyntaxTree;
            var actualErrors = errorSink.Errors.OrderBy(error => error.Location.AbsoluteIndex)
                                               .ToList();

            EvaluateRazorErrors(actualErrors, expectedErrors.ToList());
            EvaluateParseTree(rewritten, expectedOutput);
        }
        public void EvaluateData(
            TagHelperDescriptorProvider provider,
            string documentContent,
            MarkupBlock expectedOutput,
            IEnumerable <RazorError> expectedErrors)
        {
            var errorSink        = new ErrorSink();
            var results          = ParseDocument(documentContent, errorSink);
            var rewritingContext = new RewritingContext(results.Document, errorSink);

            new TagHelperParseTreeRewriter(provider).Rewrite(rewritingContext);
            var rewritten    = rewritingContext.SyntaxTree;
            var actualErrors = errorSink.Errors.OrderBy(error => error.Location.AbsoluteIndex)
                               .ToList();

            EvaluateRazorErrors(actualErrors, expectedErrors.ToList());
            EvaluateParseTree(rewritten, expectedOutput);
        }
Exemple #14
0
        public RazorSyntaxTree Execute(RazorCodeDocument document, RazorSyntaxTree syntaxTree)
        {
            var resolver = Engine.Features.OfType <TagHelperFeature>().FirstOrDefault()?.Resolver;

            if (resolver == null)
            {
                // No TagHelpers, so nothing to do.
                return(syntaxTree);
            }

            var visitor     = new TagHelperDirectiveSpanVisitor(resolver, document.ErrorSink);
            var descriptors = visitor.GetDescriptors(syntaxTree.Root);

            var rewriter = new TagHelperParseTreeRewriter(new TagHelperDescriptorProvider(descriptors));

            var context = new RewritingContext(syntaxTree.Root, document.ErrorSink);

            rewriter.Rewrite(context);
            return(RazorSyntaxTree.Create(context.SyntaxTree, context.ErrorSink.Errors));
        }
        private void BuildMalformedTagHelpers(int count, RewritingContext context)
        {
            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;

                context.ErrorSink.OnError(
                    SourceLocation.Advance(malformedTagHelper.Start, "<"),
                    RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
                        malformedTagHelper.TagName),
                    malformedTagHelper.TagName.Length);

                BuildCurrentlyTrackedTagHelperBlock(endTag: null);
            }
        }
Exemple #16
0
        public void ConditionalAttributeCollapserDoesNotRewriteEscapedTransitions()
        {
            // Act
            var results          = ParseDocument("<span foo='@@' />");
            var rewritingContext = new RewritingContext(results.Document, new ErrorSink());

            new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritingContext);
            var rewritten = rewritingContext.SyntaxTree;

            // Assert
            Assert.Equal(0, results.ParserErrors.Count());
            EvaluateParseTree(rewritten,
                              new MarkupBlock(
                                  new MarkupTagBlock(
                                      Factory.Markup("<span"),
                                      new MarkupBlock(
                                          new AttributeBlockChunkGenerator("foo", new LocationTagged <string>(" foo='", 5, 0, 5), new LocationTagged <string>("'", 13, 0, 13)),
                                          Factory.Markup(" foo='").With(SpanChunkGenerator.Null),
                                          new MarkupBlock(
                                              Factory.Markup("@").With(new LiteralAttributeChunkGenerator(new LocationTagged <string>(string.Empty, 11, 0, 11), new LocationTagged <string>("@", 11, 0, 11))).Accepts(AcceptedCharacters.None),
                                              Factory.Markup("@").With(SpanChunkGenerator.Null).Accepts(AcceptedCharacters.None)),
                                          Factory.Markup("'").With(SpanChunkGenerator.Null)),
                                      Factory.Markup(" />"))));
        }
        private void BuildMalformedTagHelpers(int count, RewritingContext context)
        {
            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;

                context.ErrorSink.OnError(
                    SourceLocation.Advance(malformedTagHelper.Start, "<"),
                    RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
                        malformedTagHelper.TagName),
                    malformedTagHelper.TagName.Length);

                BuildCurrentlyTrackedTagHelperBlock(endTag: null);
            }
        }
Exemple #18
0
        public void ConditionalAttributesDoNotCreateExtraDataForEntirelyLiteralAttribute()
        {
            // Arrange
            const string code =
                @"<div class=""sidebar"">
    <h1>Title</h1>
    <p>
        As the author, you can <a href=""/Photo/Edit/photoId"">edit</a>
        or <a href=""/Photo/Remove/photoId"">remove</a> this photo.
    </p>
    <dl>
        <dt class=""description"">Description</dt>
        <dd class=""description"">
            The uploader did not provide a description for this photo.
        </dd>
        <dt class=""uploaded-by"">Uploaded by</dt>
        <dd class=""uploaded-by""><a href=""/User/View/user.UserId"">user.DisplayName</a></dd>
        <dt class=""upload-date"">Upload date</dt>
        <dd class=""upload-date"">photo.UploadDate</dd>
        <dt class=""part-of-gallery"">Gallery</dt>
        <dd><a href=""/View/gallery.Id"" title=""View gallery.Name gallery"">gallery.Name</a></dd>
        <dt class=""tags"">Tags</dt>
        <dd class=""tags"">
            <ul class=""tags"">
                <li>This photo has no tags.</li>
            </ul>
            <a href=""/Photo/EditTags/photoId"">edit tags</a>
        </dd>
    </dl>

    <p>
        <a class=""download"" href=""/Photo/Full/photoId"" title=""Download: (photo.FileTitle + photo.FileExtension)"">Download full photo</a> ((photo.FileSize / 1024) KB)
    </p>
</div>
<div class=""main"">
    <img class=""large-photo"" alt=""photo.FileTitle"" src=""/Photo/Thumbnail"" />
    <h2>Nobody has commented on this photo</h2>
    <ol class=""comments"">
        <li>
            <h3 class=""comment-header"">
                <a href=""/User/View/comment.UserId"" title=""View comment.DisplayName's profile"">comment.DisplayName</a> commented at comment.CommentDate:
            </h3>
            <p class=""comment-body"">comment.CommentText</p>
        </li>
    </ol>

    <form method=""post"" action="""">
        <fieldset id=""addComment"">
            <legend>Post new comment</legend>
            <ol>
                <li>
                    <label for=""newComment"">Comment</label>
                    <textarea id=""newComment"" name=""newComment"" title=""Your comment"" rows=""6"" cols=""70""></textarea>
                </li>
            </ol>
            <p class=""form-actions"">
                <input type=""submit"" title=""Add comment"" value=""Add comment"" />
            </p>
        </fieldset>
    </form>
</div>";

            // Act
            var results          = ParseDocument(code);
            var rewritingContext = new RewritingContext(results.Document);

            new ConditionalAttributeCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritingContext);
            new MarkupCollapser(new HtmlMarkupParser().BuildSpan).Rewrite(rewritingContext);
            var rewritten = rewritingContext.SyntaxTree;

            // Assert
            Assert.Equal(0, results.ParserErrors.Count);
            Assert.Equal(rewritten.Children.Count(), results.Document.Children.Count());
        }
        private static bool ValidateTagStructure(string tagName, Block tag, RewritingContext context)
        {
            // We assume an invalid structure until we verify that the tag meets all of our "valid structure" criteria.
            if (IsPartialTag(tag))
            {
                context.ErrorSink.OnError(
                    tag.Start,
                    RazorResources.FormatTagHelpersParseTreeRewriter_MissingCloseAngle(tagName));

                return false;
            }

            return true;
        }
        private void BuildMalformedTagHelpers(int count, RewritingContext context)
        {
            for (var i = 0; i < count; i++)
            {
                var malformedTagHelper = _trackerStack.Peek().Builder;

                context.ErrorSink.OnError(
                    malformedTagHelper.Start,
                    RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(
                        malformedTagHelper.TagName));

                BuildCurrentlyTrackedTagHelperBlock(endTag: null);
            }
        }
        private bool TryRewriteTagHelper(Block tagBlock, RewritingContext context)
        {
            // TODO: Fully handle malformed tags: https://github.com/aspnet/Razor/issues/104

            // 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);
            }

            var descriptors = Enumerable.Empty <TagHelperDescriptor>();

            if (IsPotentialTagHelper(tagName, tagBlock))
            {
                descriptors = _provider.GetTagHelpers(tagName);
            }

            // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper
            if (!descriptors.Any())
            {
                return(false);
            }

            if (!IsEndTag(tagBlock))
            {
                // We're in a begin tag helper block

                var validTagStructure = ValidTagStructure(tagName, tagBlock, context);

                var builder = TagHelperBlockRewriter.Rewrite(tagName,
                                                             validTagStructure,
                                                             tagBlock,
                                                             descriptors,
                                                             context.ErrorSink);

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

                // If it's a self closing block then we don't have to worry about nested children
                // within the tag... complete it.
                if (IsSelfClosing(tagBlock))
                {
                    BuildCurrentlyTrackedTagHelperBlock();
                }
            }
            else
            {
                // We're in an end tag helper block.

                var tagNameScope = _tagStack.Count > 0 ? _tagStack.Peek().TagName : string.Empty;

                // Validate that our end tag helper matches the currently scoped tag helper, if not we
                // need to error.
                if (tagNameScope.Equals(tagName, StringComparison.OrdinalIgnoreCase))
                {
                    ValidTagStructure(tagName, tagBlock, context);

                    BuildCurrentlyTrackedTagHelperBlock();
                }
                else
                {
                    // 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 begin tag.
                    if (TryRecoverTagHelper(tagName, context))
                    {
                        ValidTagStructure(tagName, tagBlock, context);

                        // Successfully recovered, move onto the next element.
                    }
                    else
                    {
                        // Could not recover, the end tag helper has no corresponding begin tag, create
                        // an error based on the current childBlock.
                        context.ErrorSink.OnError(
                            tagBlock.Start,
                            RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName));

                        return(false);
                    }
                }
            }

            return(true);
        }
Exemple #22
0
        private ParserResults ParseCore(ITextDocument input)
        {
            // Setup the parser context
            var errorSink = new ErrorSink();
            var context = new ParserContext(input, CodeParser, MarkupParser, MarkupParser, errorSink)
            {
                DesignTimeMode = DesignTimeMode
            };

            MarkupParser.Context = context;
            CodeParser.Context = context;

            // Execute the parse
            MarkupParser.ParseDocument();

            // Get the result
            var results = context.CompleteParse();

            // Rewrite whitespace if supported
            var rewritingContext = new RewritingContext(results.Document, errorSink);
            foreach (ISyntaxTreeRewriter rewriter in Optimizers)
            {
                rewriter.Rewrite(rewritingContext);
            }

            var descriptors = Enumerable.Empty<TagHelperDescriptor>();

            if (TagHelperDescriptorResolver != null)
            {
                descriptors = GetTagHelperDescriptors(rewritingContext.SyntaxTree, rewritingContext.ErrorSink);
                var tagHelperProvider = new TagHelperDescriptorProvider(descriptors);

                var tagHelperParseTreeRewriter = new TagHelperParseTreeRewriter(tagHelperProvider);
                // Rewrite the document to utilize tag helpers
                tagHelperParseTreeRewriter.Rewrite(rewritingContext);
            }

            var syntaxTree = rewritingContext.SyntaxTree;

            // Link the leaf nodes into a chain
            Span prev = null;
            foreach (Span node in syntaxTree.Flatten())
            {
                node.Previous = prev;
                if (prev != null)
                {
                    prev.Next = node;
                }
                prev = node;
            }

            // Return the new result
            return new ParserResults(syntaxTree, descriptors, errorSink);
        }
Exemple #23
0
 public virtual void Rewrite(RewritingContext context)
 {
     context.SyntaxTree.Accept(this);
     Debug.Assert(_blocks.Count == 1);
     context.SyntaxTree = _blocks.Pop().Build();
 }
        public void Rewrite(RewritingContext context)
        {
            RewriteTags(context.SyntaxTree, context);

            context.SyntaxTree = _currentBlock.Build();
        }
        private bool TryRewriteTagHelper(Block tagBlock, RewritingContext context)
        {
            // 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;
            }

            var descriptors = Enumerable.Empty<TagHelperDescriptor>();

            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 providedAttributes = GetAttributeNameValuePairs(tagBlock);

                descriptors = _provider.GetDescriptors(tagName, providedAttributes, _currentParentTagName);

                // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper
                if (!descriptors.Any())
                {
                    // 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, context.ErrorSink);
                ValidateDescriptors(descriptors, tagName, tagBlock, context.ErrorSink);

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

                var builder = TagHelperBlockRewriter.Rewrite(
                    tagName,
                    validTagStructure,
                    tagBlock,
                    descriptors,
                    context.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, context);

                    BuildCurrentlyTrackedTagHelperBlock(tagBlock);
                }
                else
                {
                    descriptors = _provider.GetDescriptors(
                        tagName,
                        attributes: Enumerable.Empty<KeyValuePair<string, string>>(),
                        parentTagName: _currentParentTagName);

                    // 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 (!descriptors.Any())
                    {
                        return false;
                    }

                    var invalidDescriptor = descriptors.FirstOrDefault(
                        descriptor => descriptor.TagStructure == TagStructure.WithoutEndTag);
                    if (invalidDescriptor != null)
                    {
                        // End tag TagHelper that states it shouldn't have an end tag.
                        context.ErrorSink.OnError(
                            SourceLocation.Advance(tagBlock.Start, "</"),
                            RazorResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(
                                tagName,
                                invalidDescriptor.TypeName,
                                invalidDescriptor.TagStructure),
                            tagName.Length);

                        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, context))
                    {
                        ValidateParentAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
                        ValidateTagSyntax(tagName, tagBlock, context);

                        // 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.
                        context.ErrorSink.OnError(
                            SourceLocation.Advance(tagBlock.Start, "</"),
                            RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName),
                            tagName.Length);

                        return false;
                    }
                }
            }

            return true;
        }
        private static bool ValidateTagSyntax(string tagName, Block tag, RewritingContext context)
        {
            // We assume an invalid syntax until we verify that the tag meets all of our "valid syntax" criteria.
            if (IsPartialTag(tag))
            {
                var errorStart = GetTagDeclarationErrorStart(tag);

                context.ErrorSink.OnError(
                    errorStart,
                    RazorResources.FormatTagHelpersParseTreeRewriter_MissingCloseAngle(tagName),
                    tagName.Length);

                return false;
            }

            return true;
        }
        public void Rewrite(RewritingContext context)
        {
            RewriteTags(context.SyntaxTree, context);

            context.SyntaxTree = _currentBlock.Build();
        }
        private bool TryRewriteTagHelper(Block tagBlock, RewritingContext context)
        {
            // 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);
            }

            var descriptors = Enumerable.Empty <TagHelperDescriptor>();

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

            var tracker      = _currentTagHelperTracker;
            var tagNameScope = tracker?.Builder.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 providedAttributes = GetAttributeNames(tagBlock);

                descriptors = _provider.GetDescriptors(tagName, providedAttributes);


                // If there aren't any TagHelperDescriptors registered then we aren't a TagHelper
                if (!descriptors.Any())
                {
                    // 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);
                }

                ValidateParentTagHelperAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
                ValidateDescriptors(descriptors, tagName, tagBlock, context.ErrorSink);

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

                var builder = TagHelperBlockRewriter.Rewrite(
                    tagName,
                    validTagStructure,
                    tagBlock,
                    descriptors,
                    context.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, context);

                    BuildCurrentlyTrackedTagHelperBlock(tagBlock);
                }
                else
                {
                    descriptors = _provider.GetDescriptors(tagName, attributeNames: Enumerable.Empty <string>());

                    // 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 (!descriptors.Any())
                    {
                        return(false);
                    }

                    var invalidDescriptor = descriptors.FirstOrDefault(
                        descriptor => descriptor.TagStructure == TagStructure.WithoutEndTag);
                    if (invalidDescriptor != null)
                    {
                        // End tag TagHelper that states it shouldn't have an end tag.
                        context.ErrorSink.OnError(
                            tagBlock.Start,
                            RazorResources.FormatTagHelperParseTreeRewriter_EndTagTagHelperMustNotHaveAnEndTag(
                                tagName,
                                invalidDescriptor.TypeName,
                                invalidDescriptor.TagStructure),
                            tagBlock.Length);

                        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, context))
                    {
                        ValidateParentTagHelperAllowsTagHelper(tagName, tagBlock, context.ErrorSink);
                        ValidateTagSyntax(tagName, tagBlock, context);

                        // 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.
                        context.ErrorSink.OnError(
                            tagBlock.Start,
                            RazorResources.FormatTagHelpersParseTreeRewriter_FoundMalformedTagHelper(tagName));

                        return(false);
                    }
                }
            }

            return(true);
        }
        private bool TryRecoverTagHelper(string tagName, Block endTag, RewritingContext context)
        {
            var malformedTagHelperCount = 0;

            foreach (var tracker in _trackerStack)
            {
                if (tracker.IsTagHelper && tracker.TagName.Equals(tagName, StringComparison.OrdinalIgnoreCase))
                {
                    break;
                }

                malformedTagHelperCount++;
            }

            // If the malformedTagHelperCount == _tagStack.Count it means we couldn't find a start tag for the tag
            // helper, can't recover.
            if (malformedTagHelperCount != _trackerStack.Count)
            {
                BuildMalformedTagHelpers(malformedTagHelperCount, context);

                // One final build, this is the build that completes our target tag helper block which is not malformed.
                BuildCurrentlyTrackedTagHelperBlock(endTag);

                // We were able to recover
                return true;
            }

            // Could not recover tag helper. Aka we found a tag helper end tag without a corresponding start tag.
            return false;
        }