private void ValidateParentAllowsPlainEndTag(MarkupEndTagSyntax tagBlock) { var tagName = tagBlock.GetTagNameWithOptionalBang(); // Treat partial tags such as '</' which have no tag names as content. if (string.IsNullOrEmpty(tagName)) { var firstChild = tagBlock.Children.First(); Debug.Assert(firstChild is MarkupTextLiteralSyntax); ValidateParentAllowsContent(firstChild); return; } if (!HasAllowedChildren()) { return; } var tagHelperBinding = _tagHelperBinder.GetBinding( tagName, attributes: Array.Empty <KeyValuePair <string, string> >(), parentTagName: CurrentParentTagName, parentIsTagHelper: CurrentParentIsTagHelper); // If we found a binding for the current tag, then it is a tag helper. Use the prefixed allowed children to compare. var allowedChildren = tagHelperBinding != null ? CurrentTagHelperTracker.PrefixedAllowedChildren : CurrentTagHelperTracker.AllowedChildren; if (!allowedChildren.Contains(tagName, StringComparer.OrdinalIgnoreCase)) { OnAllowedChildrenEndTagError(CurrentTagHelperTracker, tagName, tagBlock, _errorSink, _source); } }
private bool IsPotentialTagHelperEnd(string tagName, MarkupEndTagSyntax childBlock) { Debug.Assert(childBlock.Children.Count > 0); var child = childBlock.Children[0]; return(!string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) || child.Kind != SyntaxKind.MarkupTransition); }
public override void VisitMarkupEndTag(MarkupEndTagSyntax node) { WriteBlock(node, FormattingBlockKind.Tag, n => { var children = GetRewrittenMarkupEndTagChildren(node); foreach (var child in children) { Visit(child); } }); }
public override void VisitMarkupEndTag(MarkupEndTagSyntax node) { AddSemanticRange(node.OpenAngle); if (node.Bang != null) { AddSemanticRange(node.Bang); } if (node.ForwardSlash != null) { AddSemanticRange(node.ForwardSlash); } AddSemanticRange(node.Name, SyntaxKind.MarkupElement); AddSemanticRange(node.CloseAngle); base.VisitMarkupEndTag(node); }
public override void VisitMarkupEndTag(MarkupEndTagSyntax node) { AddSemanticRange(node.OpenAngle, RazorSemanticTokensLegend.MarkupTagDelimiter); if (node.Bang != null) { AddSemanticRange(node.Bang, RazorSemanticTokensLegend.MarkupElement); } if (node.ForwardSlash != null) { AddSemanticRange(node.ForwardSlash, RazorSemanticTokensLegend.MarkupTagDelimiter); } AddSemanticRange(node.Name, RazorSemanticTokensLegend.MarkupElement); AddSemanticRange(node.CloseAngle, RazorSemanticTokensLegend.MarkupTagDelimiter); }
private bool ValidateEndTagSyntax(string tagName, MarkupEndTagSyntax tag) { // We assume an invalid syntax until we verify that the tag meets all of our "valid syntax" criteria. if (IsPartialEndTag(tag)) { var errorStart = GetEndTagDeclarationErrorStart(tag, _source); _errorSink.OnError( RazorDiagnosticFactory.CreateParsing_TagHelperMissingCloseAngle( new SourceSpan(errorStart, tagName.Length), tagName)); return(false); } return(true); }
private static void OnAllowedChildrenEndTagError( TagHelperTracker tracker, string tagName, MarkupEndTagSyntax tagBlock, ErrorSink errorSink, RazorSourceDocument source) { var allowedChildrenString = string.Join(", ", tracker.AllowedChildren); var errorStart = GetEndTagDeclarationErrorStart(tagBlock, source); errorSink.OnError( RazorDiagnosticFactory.CreateTagHelper_InvalidNestedTag( new SourceSpan(errorStart, tagName.Length), tagName, tracker.TagName, allowedChildrenString)); }
public static TagMode GetTagMode( MarkupStartTagSyntax startTag, MarkupEndTagSyntax endTag, TagHelperBinding bindingResult) { var childSpan = startTag.GetLastToken()?.Parent; // Self-closing tags are always valid despite descriptors[X].TagStructure. if (childSpan?.GetContent().EndsWith("/>", StringComparison.Ordinal) ?? false) { return(TagMode.SelfClosing); } var hasDirectiveAttribute = false; foreach (var descriptor in bindingResult.Descriptors) { var boundRules = bindingResult.Mappings[descriptor]; var nonDefaultRule = boundRules.FirstOrDefault(rule => rule.TagStructure != TagStructure.Unspecified); if (nonDefaultRule?.TagStructure == TagStructure.WithoutEndTag) { return(TagMode.StartTagOnly); } // Directive attribute will tolerate forms that don't work for tag helpers. For instance: // // <input @onclick="..."> vs <input onclick="..." /> // // We don't want this to become an error just because you added a directive attribute. if (descriptor.IsAnyComponentDocumentTagHelper() && !descriptor.IsComponentOrChildContentTagHelper()) { hasDirectiveAttribute = true; } } if (hasDirectiveAttribute && startTag.IsVoidElement() && endTag == null) { return(TagMode.StartTagOnly); } return(TagMode.StartTagAndEndTag); }
private static bool IsPartialEndTag(MarkupEndTagSyntax tagBlock) { // No need to validate the tag end because in order to be a tag block it must start with '<'. var tagEnd = tagBlock.Children[tagBlock.Children.Count - 1]; // 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 is MarkupTextLiteralSyntax tagEndLiteral) { var endToken = tagEndLiteral.LiteralTokens.Count > 0 ? tagEndLiteral.LiteralTokens[tagEndLiteral.LiteralTokens.Count - 1] : null; if (endToken != null && endToken.Kind == SyntaxKind.CloseAngle) { return(false); } } return(true); }
private static SyntaxList <RazorSyntaxNode> GetRewrittenMarkupEndTagChildren(MarkupEndTagSyntax node) { // Rewrites the children of the end tag to look like the legacy syntax tree. if (node.IsMarkupTransition) { var tokens = node.DescendantNodes().Where(n => n is SyntaxToken token && !token.IsMissing).Cast <SyntaxToken>().ToArray(); var tokenBuilder = SyntaxListBuilder <SyntaxToken> .Create(); tokenBuilder.AddRange(tokens, 0, tokens.Length); var markupTransition = SyntaxFactory.MarkupTransition(tokenBuilder.ToList()).Green.CreateRed(node, node.Position); var spanContext = node.GetSpanContext(); if (spanContext != null) { markupTransition = markupTransition.WithSpanContext(spanContext); } var builder = new SyntaxListBuilder(1); builder.Add(markupTransition); return(new SyntaxList <RazorSyntaxNode>(builder.ToListNode().CreateRed(node, node.Position))); } return(node.Children); }
private static bool IsPartialEndTag(MarkupEndTagSyntax endTag) { return(endTag.CloseAngle.IsMissing); }
private bool IsPotentialTagHelperEnd(string tagName, MarkupEndTagSyntax endTag) { return(!string.Equals(tagName, SyntaxConstants.TextTagName, StringComparison.OrdinalIgnoreCase) || !endTag.IsMarkupTransition); }
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 bool TryRewriteTagHelperStart( MarkupStartTagSyntax startTag, MarkupEndTagSyntax endTag, out MarkupTagHelperStartTagSyntax rewritten, out TagHelperInfo tagHelperInfo) { rewritten = null; tagHelperInfo = null; // Get tag name of the current block var tagName = startTag.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); } TagHelperBinding tagHelperBinding; if (!IsPotentialTagHelperStart(tagName, startTag)) { return(false); } var tracker = CurrentTagHelperTracker; var tagNameScope = tracker?.TagName ?? string.Empty; // We're now in a start tag block, we first need to see if the tag block is a tag helper. var elementAttributes = GetAttributeNameValuePairs(startTag); 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, startTag); ValidateBinding(tagHelperBinding, tagName, startTag); // We're in a start TagHelper block. ValidateStartTagSyntax(tagName, startTag); var rewrittenStartTag = TagHelperBlockRewriter.Rewrite( tagName, _featureFlags, startTag, tagHelperBinding, _errorSink, _source); var tagMode = TagHelperBlockRewriter.GetTagMode(startTag, endTag, tagHelperBinding); tagHelperInfo = new TagHelperInfo(tagName, tagMode, tagHelperBinding); rewritten = rewrittenStartTag; return(true); }
public override void VisitMarkupEndTag(MarkupEndTagSyntax node) { WriteNode(node, isHtml: true, base.VisitMarkupEndTag); }
private static SourceLocation GetEndTagDeclarationErrorStart(MarkupEndTagSyntax tagBlock, RazorSourceDocument source) { return(SourceLocationTracker.Advance(tagBlock.GetSourceLocation(source), "</")); }
public override void VisitMarkupEndTag(MarkupEndTagSyntax node) { WriteBlock(node, BlockKindInternal.Tag, base.VisitMarkupEndTag); }