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