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