diff --git a/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs b/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs index 92916c8d48..d032003c77 100644 --- a/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs +++ b/src/Microsoft.AspNetCore.Razor.Runtime/Runtime/TagHelpers/TagHelperDescriptorFactory.cs @@ -190,21 +190,21 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers foreach (var name in allowedChildren) { - var valid = TryValidateName( + if (string.IsNullOrWhiteSpace(name)) + { + var whitespaceError = Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace( + nameof(RestrictChildrenAttribute), + tagHelperName); + errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0); + } + else if (TryValidateName( name, - whitespaceError: - Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeNameNullWhitespace( - nameof(RestrictChildrenAttribute), - tagHelperName), - characterErrorBuilder: (invalidCharacter) => - Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName( - nameof(RestrictChildrenAttribute), - name, - tagHelperName, - invalidCharacter), - errorSink: errorSink); - - if (valid) + invalidCharacter => Resources.FormatTagHelperDescriptorFactory_InvalidRestrictChildrenAttributeName( + nameof(RestrictChildrenAttribute), + name, + tagHelperName, + invalidCharacter), + errorSink)) { validAllowedChildren.Add(name); } @@ -281,17 +281,29 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers /// internal static bool ValidateParentTagName(string parentTag, ErrorSink errorSink) { - return parentTag == null || - TryValidateName( + if (parentTag == null) + { + return true; + } + else if (string.IsNullOrWhiteSpace(parentTag)) + { + var error = Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace( + Resources.TagHelperDescriptorFactory_ParentTag); + errorSink.OnError(SourceLocation.Zero, error, length: 0); + return false; + } + else if (!TryValidateName( + parentTag, + invalidCharacter => Resources.FormatHtmlTargetElementAttribute_InvalidName( + Resources.TagHelperDescriptorFactory_ParentTag.ToLower(), parentTag, - Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace( - Resources.TagHelperDescriptorFactory_ParentTag), - characterErrorBuilder: (invalidCharacter) => - Resources.FormatHtmlTargetElementAttribute_InvalidName( - Resources.TagHelperDescriptorFactory_ParentTag.ToLower(), - parentTag, - invalidCharacter), - errorSink: errorSink); + invalidCharacter), + errorSink)) + { + return false; + } + + return true; } private static bool TryGetRequiredAttributeDescriptors( @@ -320,45 +332,42 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers Resources.TagHelperDescriptorFactory_Attribute : Resources.TagHelperDescriptorFactory_Tag; - var validName = TryValidateName( + if (string.IsNullOrWhiteSpace(name)) + { + var error = Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName); + errorSink.OnError(SourceLocation.Zero, error, length: 0); + return false; + } + else if (!TryValidateName( name, - whitespaceError: Resources.FormatHtmlTargetElementAttribute_NameCannotBeNullOrWhitespace(targetName), - characterErrorBuilder: (invalidCharacter) => - Resources.FormatHtmlTargetElementAttribute_InvalidName( - targetName.ToLower(), - name, - invalidCharacter), - errorSink: errorSink); + invalidCharacter => Resources.FormatHtmlTargetElementAttribute_InvalidName( + targetName.ToLower(), + name, + invalidCharacter), + errorSink)) + { + return false; + } - return validName; + return true; } private static bool TryValidateName( string name, - string whitespaceError, Func characterErrorBuilder, ErrorSink errorSink) { var validName = true; - if (string.IsNullOrWhiteSpace(name)) + foreach (var character in name) { - errorSink.OnError(SourceLocation.Zero, whitespaceError, length: 0); - - validName = false; - } - else - { - foreach (var character in name) + if (char.IsWhiteSpace(character) || + InvalidNonWhitespaceNameCharacters.Contains(character)) { - if (char.IsWhiteSpace(character) || - InvalidNonWhitespaceNameCharacters.Contains(character)) - { - var error = characterErrorBuilder(character); - errorSink.OnError(SourceLocation.Zero, error, length: 0); + var error = characterErrorBuilder(character); + errorSink.OnError(SourceLocation.Zero, error, length: 0); - validName = false; - } + validName = false; } } @@ -696,7 +705,7 @@ namespace Microsoft.AspNetCore.Razor.Runtime.TagHelpers return property.GetIndexParameters().Length == 0 && property.GetMethod != null && property.GetMethod.IsPublic && - property.GetCustomAttribute(inherit: false) == null; + !property.IsDefined(typeof(HtmlAttributeNotBoundAttribute), inherit: false); } /// diff --git a/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorProvider.cs b/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorProvider.cs index 47ef9da3b5..d3b540e060 100644 --- a/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Razor/Compilation/TagHelpers/TagHelperDescriptorProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNetCore.Razor.Parser.TagHelpers; namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers { @@ -77,29 +76,34 @@ namespace Microsoft.AspNetCore.Razor.Compilation.TagHelpers descriptors = matchingDescriptors.Concat(descriptors); } - var applicableDescriptors = ApplyRequiredAttributes(descriptors, attributes); - applicableDescriptors = ApplyParentTagFilter(applicableDescriptors, parentTagName); + var applicableDescriptors = new List(); + foreach (var descriptor in descriptors) + { + if (HasRequiredAttributes(descriptor, attributes) && + HasRequiredParentTag(descriptor, parentTagName)) + { + applicableDescriptors.Add(descriptor); + } + } - return applicableDescriptors.ToArray(); + return applicableDescriptors; } - private IEnumerable ApplyParentTagFilter( - IEnumerable descriptors, + private bool HasRequiredParentTag( + TagHelperDescriptor descriptor, string parentTagName) { - return descriptors.Where(descriptor => - descriptor.RequiredParent == null || - string.Equals(parentTagName, descriptor.RequiredParent, StringComparison.OrdinalIgnoreCase)); + return descriptor.RequiredParent == null || + string.Equals(parentTagName, descriptor.RequiredParent, StringComparison.OrdinalIgnoreCase); } - private IEnumerable ApplyRequiredAttributes( - IEnumerable descriptors, + private bool HasRequiredAttributes( + TagHelperDescriptor descriptor, IEnumerable> attributes) { - return descriptors.Where( - descriptor => descriptor.RequiredAttributes.All( - requiredAttribute => attributes.Any( - attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value)))); + return descriptor.RequiredAttributes.All( + requiredAttribute => attributes.Any( + attribute => requiredAttribute.IsMatch(attribute.Key, attribute.Value))); } private void Register(TagHelperDescriptor descriptor) diff --git a/src/Microsoft.AspNetCore.Razor/Parser/ConditionalAttributeCollapser.cs b/src/Microsoft.AspNetCore.Razor/Parser/ConditionalAttributeCollapser.cs index 0431ce2adb..e870fecad0 100644 --- a/src/Microsoft.AspNetCore.Razor/Parser/ConditionalAttributeCollapser.cs +++ b/src/Microsoft.AspNetCore.Razor/Parser/ConditionalAttributeCollapser.cs @@ -3,10 +3,9 @@ using System; using System.Diagnostics; -using System.Linq; +using System.Text; using Microsoft.AspNetCore.Razor.Chunks.Generators; using Microsoft.AspNetCore.Razor.Editor; -using Microsoft.AspNetCore.Razor.Parser.Internal; using Microsoft.AspNetCore.Razor.Parser.SyntaxTree; using Microsoft.AspNetCore.Razor.Tokenizer.Internal; @@ -41,12 +40,19 @@ namespace Microsoft.AspNetCore.Razor.Parser protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block) { // Collect the content of this node - var content = string.Concat(block.Children.Cast().Select(s => s.Content)); + var builder = new StringBuilder(); + for (var i = 0; i < block.Children.Count; i++) + { + var childSpan = (Span)block.Children[i]; + builder.Append(childSpan.Content); + } // Create a new span containing this content var span = new SpanBuilder(); span.EditHandler = new SpanEditHandler(HtmlTokenizer.Tokenize); - FillSpan(span, block.Children.Cast().First().Start, content); + Debug.Assert(block.Children.Count > 0); + var start = ((Span)block.Children[0]).Start; + FillSpan(span, start, builder.ToString()); return span.Build(); } diff --git a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperBlockRewriter.cs b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperBlockRewriter.cs index 2a786f8460..581955b4f2 100644 --- a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperBlockRewriter.cs +++ b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperBlockRewriter.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Text; using Microsoft.AspNetCore.Razor.Chunks.Generators; using Microsoft.AspNetCore.Razor.Compilation.TagHelpers; using Microsoft.AspNetCore.Razor.Editor; @@ -207,15 +208,23 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal // We're now at: " |asp-for='...'" or " |asp-for=..." // The goal here is to capture the attribute name. - var symbolContents = htmlSymbols - .Skip(i) // Skip prefix - .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol)) - .Select(nameSymbol => nameSymbol.Content); - + var nameBuilder = new StringBuilder(); // Move the indexer past the attribute name symbols. - i += symbolContents.Count() - 1; + for (var j = i; j < htmlSymbols.Length; j++) + { + var nameSymbol = htmlSymbols[j]; + if (!HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol)) + { + break; + } - name = string.Concat(symbolContents); + nameBuilder.Append(nameSymbol.Content); + i++; + } + + i--; + + name = nameBuilder.ToString(); attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, name); } else if (symbol.Type == HtmlSymbolType.Equals) diff --git a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperParseTreeRewriter.cs b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperParseTreeRewriter.cs index 925d4d146c..af46909326 100644 --- a/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperParseTreeRewriter.cs +++ b/src/Microsoft.AspNetCore.Razor/Parser/TagHelpers/Internal/TagHelperParseTreeRewriter.cs @@ -597,12 +597,14 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal private static bool IsPartialTag(Block tagBlock) { // No need to validate the tag end because in order to be a tag block it must start with '<'. - var tagEnd = tagBlock.Children.Last() as Span; + var tagEnd = tagBlock.Children[tagBlock.Children.Count - 1] 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; + var endSymbol = tagEnd.Symbols.Count > 0 ? + tagEnd.Symbols[tagEnd.Symbols.Count - 1] as HtmlSymbol : + null; if (endSymbol != null && endSymbol.Type == HtmlSymbolType.CloseAngle) { @@ -661,8 +663,8 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal private bool IsPotentialTagHelper(string tagName, Block childBlock) { - var child = childBlock.Children.FirstOrDefault(); - Debug.Assert(child != null); + Debug.Assert(childBlock.Children.Count > 0); + var child = childBlock.Children[0]; var childSpan = (Span)child; @@ -752,7 +754,18 @@ namespace Microsoft.AspNetCore.Razor.Parser.TagHelpers.Internal } var childSpan = (Span)child; - var textSymbol = childSpan.Symbols.FirstHtmlSymbolAs(HtmlSymbolType.WhiteSpace | HtmlSymbolType.Text); + HtmlSymbol textSymbol = null; + for (var i = 0; i < childSpan.Symbols.Count; i++) + { + var symbol = childSpan.Symbols[i] as HtmlSymbol; + + if (symbol != null && + (symbol.Type & (HtmlSymbolType.WhiteSpace | HtmlSymbolType.Text)) == symbol.Type) + { + textSymbol = symbol; + break; + } + } if (textSymbol == null) { diff --git a/src/Microsoft.AspNetCore.Razor/Text/LineTrackingStringBuffer.cs b/src/Microsoft.AspNetCore.Razor/Text/LineTrackingStringBuffer.cs index 47ab80bef3..202cad89b9 100644 --- a/src/Microsoft.AspNetCore.Razor/Text/LineTrackingStringBuffer.cs +++ b/src/Microsoft.AspNetCore.Razor/Text/LineTrackingStringBuffer.cs @@ -33,17 +33,24 @@ namespace Microsoft.AspNetCore.Razor.Text public void Append(string content) { - for (int i = 0; i < content.Length; i++) + var previousIndex = 0; + for (var i = 0; i < content.Length; i++) { - AppendCore(content[i]); - // \r on it's own: Start a new line, otherwise wait for \n // Other Newline: Start a new line - if ((content[i] == '\r' && (i + 1 == content.Length || content[i + 1] != '\n')) || (content[i] != '\r' && ParserHelpers.IsNewLine(content[i]))) + if ((content[i] == '\r' && (i + 1 == content.Length || content[i + 1] != '\n')) || + (content[i] != '\r' && ParserHelpers.IsNewLine(content[i]))) { + AppendCore(content, previousIndex, i - previousIndex + 1); + previousIndex = i + 1; PushNewLine(); } } + + if (previousIndex < content.Length) + { + AppendCore(content, previousIndex, content.Length - previousIndex); + } } public CharacterReference CharAt(int absoluteIndex) @@ -63,6 +70,12 @@ namespace Microsoft.AspNetCore.Razor.Text _lines.Add(_endLine); } + private void AppendCore(string content, int index, int length) + { + Debug.Assert(_lines.Count > 0); + _lines[_lines.Count - 1].Content.Append(content, index, length); + } + private void AppendCore(char chr) { Debug.Assert(_lines.Count > 0); diff --git a/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/Internal/HtmlSymbolExtensions.cs b/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/Internal/HtmlSymbolExtensions.cs deleted file mode 100644 index 2ba3630d14..0000000000 --- a/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/Internal/HtmlSymbolExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.AspNetCore.Razor.Tokenizer.Symbols.Internal -{ - public static class HtmlSymbolExtensions - { - /// - /// Converts the generic to a and - /// finds the first with type . - /// - /// The instance this method extends. - /// The to search for. - /// The first of type . - public static HtmlSymbol FirstHtmlSymbolAs(this IEnumerable symbols, HtmlSymbolType type) - { - return symbols.OfType().FirstOrDefault(sym => (type & sym.Type) == sym.Type); - } - } -} diff --git a/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/SymbolExtensions.cs b/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/SymbolExtensions.cs index 33ad1478df..65ae598176 100644 --- a/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/SymbolExtensions.cs +++ b/src/Microsoft.AspNetCore.Razor/Tokenizer/Symbols/SymbolExtensions.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Text; using Microsoft.AspNetCore.Razor.Parser.SyntaxTree; using Microsoft.AspNetCore.Razor.Text; @@ -13,7 +13,21 @@ namespace Microsoft.AspNetCore.Razor.Tokenizer.Symbols { public static LocationTagged GetContent(this SpanBuilder span) { - return GetContent(span, e => e); + var symbols = span.Symbols; + if (symbols.Count > 0) + { + var text = new StringBuilder(); + for (var i = 0; i < symbols.Count; i++) + { + text.Append(symbols[i].Content); + } + + return new LocationTagged(text.ToString(), span.Start + symbols[0].Start); + } + else + { + return new LocationTagged(string.Empty, span.Start); + } } public static LocationTagged GetContent(this SpanBuilder span, Func, IEnumerable> filter) @@ -23,14 +37,21 @@ namespace Microsoft.AspNetCore.Razor.Tokenizer.Symbols public static LocationTagged GetContent(this IEnumerable symbols, SourceLocation spanStart) { - if (symbols.Any()) + StringBuilder builder = null; + var location = spanStart; + + foreach (var symbol in symbols) { - return new LocationTagged(string.Concat(symbols.Select(s => s.Content)), spanStart + symbols.First().Start); - } - else - { - return new LocationTagged(string.Empty, spanStart); + if (builder == null) + { + builder = new StringBuilder(); + location += symbol.Start; + } + + builder.Append(symbol.Content); } + + return new LocationTagged(builder?.ToString() ?? string.Empty, location); } public static LocationTagged GetContent(this ISymbol symbol)