private static SyntaxList <RazorSyntaxNode> GetRewrittenMarkupStartTagChildren(MarkupStartTagSyntax node) { // Rewrites the children of the start 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))); } SpanContext latestSpanContext = null; var children = node.Children; var newChildren = new SyntaxListBuilder(children.Count); var literals = new List <MarkupTextLiteralSyntax>(); foreach (var child in children) { if (child is MarkupTextLiteralSyntax literal) { literals.Add(literal); latestSpanContext = literal.GetSpanContext() ?? latestSpanContext; } else if (child is MarkupMiscAttributeContentSyntax miscContent) { foreach (var contentChild in miscContent.Children) { if (contentChild is MarkupTextLiteralSyntax contentLiteral) { literals.Add(contentLiteral); latestSpanContext = contentLiteral.GetSpanContext() ?? latestSpanContext; } else { // Pop stack AddLiteralIfExists(); newChildren.Add(contentChild); } } } else { AddLiteralIfExists(); newChildren.Add(child); } } AddLiteralIfExists(); return(new SyntaxList <RazorSyntaxNode>(newChildren.ToListNode().CreateRed(node, node.Position))); void AddLiteralIfExists() { if (literals.Count > 0) { var mergedLiteral = SyntaxUtilities.MergeTextLiterals(literals.ToArray()); mergedLiteral = mergedLiteral.WithSpanContext(latestSpanContext); literals.Clear(); latestSpanContext = null; newChildren.Add(mergedLiteral); } } }
public static MarkupTagHelperStartTagSyntax Rewrite( string tagName, RazorParserFeatureFlags featureFlags, MarkupStartTagSyntax startTag, TagHelperBinding bindingResult, ErrorSink errorSink, RazorSourceDocument source) { var processedBoundAttributeNames = new HashSet <string>(StringComparer.OrdinalIgnoreCase); var attributes = startTag.Attributes; var attributeBuilder = SyntaxListBuilder <RazorSyntaxNode> .Create(); for (var i = 0; i < startTag.Attributes.Count; i++) { var isMinimized = false; var attributeNameLocation = SourceLocation.Undefined; var child = startTag.Attributes[i]; TryParseResult result; if (child is MarkupAttributeBlockSyntax attributeBlock) { attributeNameLocation = attributeBlock.Name.GetSourceLocation(source); result = TryParseAttribute( tagName, attributeBlock, bindingResult.Descriptors, errorSink, processedBoundAttributeNames); attributeBuilder.Add(result.RewrittenAttribute); } else if (child is MarkupMinimizedAttributeBlockSyntax minimizedAttributeBlock) { isMinimized = true; attributeNameLocation = minimizedAttributeBlock.Name.GetSourceLocation(source); result = TryParseMinimizedAttribute( tagName, minimizedAttributeBlock, bindingResult.Descriptors, errorSink, processedBoundAttributeNames); attributeBuilder.Add(result.RewrittenAttribute); } else if (child is MarkupMiscAttributeContentSyntax miscContent) { foreach (var contentChild in miscContent.Children) { if (contentChild is CSharpCodeBlockSyntax codeBlock) { // TODO: Accept more than just Markup attributes: https://github.com/aspnet/Razor/issues/96. // Something like: // <input @checked /> var location = new SourceSpan(codeBlock.GetSourceLocation(source), codeBlock.FullWidth); var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelpersCannotHaveCSharpInTagDeclaration(location, tagName); errorSink.OnError(diagnostic); break; } else { // If the original span content was whitespace it ultimately means the tag // that owns this "attribute" is malformed and is expecting a user to type a new attribute. // ex: <myTH class="btn"| | var literalContent = contentChild.GetContent(); if (!string.IsNullOrWhiteSpace(literalContent)) { var location = contentChild.GetSourceSpan(source); var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelperAttributeListMustBeWellFormed(location); errorSink.OnError(diagnostic); break; } } } result = null; } else { result = null; } // Only want to track the attribute if we succeeded in parsing its corresponding Block/Span. if (result == null) { // Error occurred while parsing the attribute. Don't try parsing the rest to avoid misleading errors. for (var j = i; j < startTag.Attributes.Count; j++) { attributeBuilder.Add(startTag.Attributes[j]); } break; } // Check if it's a non-boolean bound attribute that is minimized or if it's a bound // non-string attribute that has null or whitespace content. var isValidMinimizedAttribute = featureFlags.AllowMinimizedBooleanTagHelperAttributes && result.IsBoundBooleanAttribute; if ((isMinimized && result.IsBoundAttribute && !isValidMinimizedAttribute) || (!isMinimized && result.IsBoundNonStringAttribute && string.IsNullOrWhiteSpace(GetAttributeValueContent(result.RewrittenAttribute)))) { var errorLocation = new SourceSpan(attributeNameLocation, result.AttributeName.Length); var propertyTypeName = GetPropertyType(result.AttributeName, bindingResult.Descriptors); var diagnostic = RazorDiagnosticFactory.CreateTagHelper_EmptyBoundAttribute(errorLocation, result.AttributeName, tagName, propertyTypeName); errorSink.OnError(diagnostic); } // Check if the attribute was a prefix match for a tag helper dictionary property but the // dictionary key would be the empty string. if (result.IsMissingDictionaryKey) { var errorLocation = new SourceSpan(attributeNameLocation, result.AttributeName.Length); var diagnostic = RazorDiagnosticFactory.CreateParsing_TagHelperIndexerAttributeNameMustIncludeKey(errorLocation, result.AttributeName, tagName); errorSink.OnError(diagnostic); } } if (attributeBuilder.Count > 0) { // This means we rewrote something. Use the new set of attributes. attributes = attributeBuilder.ToList(); } var tagHelperStartTag = SyntaxFactory.MarkupTagHelperStartTag( startTag.OpenAngle, startTag.Bang, startTag.Name, attributes, startTag.ForwardSlash, startTag.CloseAngle); return(tagHelperStartTag.WithSpanContext(startTag.GetSpanContext())); }