public void OptimizeSpan(Span span)
        {
            Require.NotNull(span, nameof(span));

            var builder = new SpanBuilder(span);

            builder.ClearSymbols();

            var prevType = HtmlSymbolType.Unknown;

            foreach (ISymbol item in span.Symbols)
            {
                var sym = item as HtmlSymbol;

                if (sym == null)
                {
                    // On ne touche pas les éléments qui ne sont pas de type HtmlSymbol.
                    builder.Accept(item);
                    continue;
                }

                // FIXME: On perd toute information contextuelle. Peut-être pour remédier à ce problème
                // on pourrait ré-utiliser un HtmlSymbol.
                builder.Accept(new Symbol_ {
                    Content = OptimizeContent(sym, prevType)
                });
                prevType = sym.Type;
            }

            span.ReplaceWith(builder);
        }
        public void IsCSharpOpenCurlyBrace_MultipleSymbols_ReturnFalse()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);
            childBuilder.Accept(SyntaxFactory.Token(SyntaxKind.Identifier, "hello"));
            childBuilder.Accept(SyntaxFactory.Token(SyntaxKind.Comma, ","));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
Exemple #3
0
        public void IsCSharpOpenCurlyBrace_MultipleSymbols_ReturnFalse()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);

            childBuilder.Accept(new CSharpSymbol("hello", CSharpSymbolType.Identifier));
            childBuilder.Accept(new CSharpSymbol(",", CSharpSymbolType.Comma));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
 public override void BuildSpan(SpanBuilder span, SourceLocation start, string content)
 {
     foreach (ISymbol sym in Language.TokenizeString(start, content))
     {
         span.Accept(sym);
     }
 }
Exemple #5
0
        public void AddSpanAddsSpanToCurrentBlockBuilder()
        {
            // Arrange
            var factory = SpanFactory.CreateCsHtml();
            var mockListener = new Mock<ParserVisitor>();
            var context = SetupTestContext("phoo");

            var builder = new SpanBuilder()
            {
                Kind = SpanKind.Code
            };
            builder.Accept(new CSharpSymbol(1, 0, 1, "foo", CSharpSymbolType.Identifier));
            var added = builder.Build();

            using (context.StartBlock(BlockType.Functions))
            {
                context.AddSpan(added);
            }

            var expected = new BlockBuilder()
            {
                Type = BlockType.Functions,
            };
            expected.Children.Add(added);

            // Assert
            ParserTestBase.EvaluateResults(context.CompleteParse(), expected.Build());
        }
        public override void VisitSpan(Span span)
        {
            if (span.Kind == SpanKind.Markup)
            {
                var content = span.Content;

                content = _minifier.Minify(content);

                var builder = new SpanBuilder
                {
                    CodeGenerator = span.CodeGenerator,
                    EditHandler   = span.EditHandler,
                    Kind          = span.Kind,
                    Start         = span.Start
                };
                var symbol = new MarkupSymbol
                {
                    Content = content
                };
                builder.Accept(symbol);
                span.ReplaceWith(builder);
            }

            base.VisitSpan(span);
        }
Exemple #7
0
        public void AddSpanAddsSpanToCurrentBlockBuilder()
        {
            // Arrange
            var factory = SpanFactory.CreateCsHtml();
            Mock <ParserVisitor> mockListener = new Mock <ParserVisitor>();
            var context = SetupTestContext("phoo");

            var builder = new SpanBuilder()
            {
                Kind = SpanKind.Code
            };

            builder.Accept(new CSharpSymbol(1, 0, 1, "foo", CSharpSymbolType.Identifier));
            var added = builder.Build();

            using (context.StartBlock(BlockType.Functions))
            {
                context.AddSpan(added);
            }

            var expected = new BlockBuilder()
            {
                Type = BlockType.Functions,
            };

            expected.Children.Add(added);

            // Assert
            ParserTestBase.EvaluateResults(context.CompleteParse(), expected.Build());
        }
Exemple #8
0
        public void AddLiteralChunk(string literal, SyntaxTreeNode association)
        {
            // If the previous chunk was also a LiteralChunk, append the content of the current node to the previous one.
            var literalChunk = _lastChunk as LiteralChunk;

            if (literalChunk != null)
            {
                // Literal chunks are always associated with Spans
                var lastSpan    = (Span)literalChunk.Association;
                var currentSpan = (Span)association;

                var builder = new SpanBuilder(lastSpan);
                foreach (var symbol in currentSpan.Symbols)
                {
                    builder.Accept(symbol);
                }

                literalChunk.Association = builder.Build();
                literalChunk.Text       += literal;
            }
            else
            {
                AddChunk(new LiteralChunk
                {
                    Text = literal,
                }, association);
            }
        }
        public void GetEditedContent_Span_ReturnsNewContent()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(0, 0, 0));

            builder.Accept(new RawTextSymbol(new SourceLocation(0, 0, 0), "Hello, "));
            builder.Accept(new RawTextSymbol(new SourceLocation(7, 0, 7), "World"));

            var span = new Span(builder);

            var change = new SourceChange(2, 2, "heyo");

            // Act
            var result = change.GetEditedContent(span);

            // Act
            Assert.Equal("Heheyoo, World", result);
        }
        public void GetOrigninalText_SpanIsOwner_ReturnsContent_ZeroLengthSpan()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(13, 0, 0));

            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "Hello, "));
            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "World"));

            var span = new Span(builder);

            var change = new SourceChange(15, 0, "heyo");

            // Act
            var result = change.GetOriginalText(span);

            // Act
            Assert.Equal(string.Empty, result);
        }
        public void GetEditedContent_Span_ReturnsNewContent()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(0, 0, 0));

            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "Hello, "));
            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "World"));

            var span = new Span(builder);

            var change = new SourceChange(2, 2, "heyo");

            // Act
            var result = change.GetEditedContent(span);

            // Act
            Assert.Equal("Heheyoo, World", result);
        }
        public void GetOffSet_SpanIsOwner_ReturnsOffset()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(13, 0, 0));

            builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
            builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));

            var span = new Span(builder);

            var change = new SourceChange(15, 2, "heyo");

            // Act
            var result = change.GetOffset(span);

            // Act
            Assert.Equal(2, result);
        }
        // This method handles cases when the attribute is a simple span attribute such as
        // class="something moresomething".  This does not handle complex attributes such as
        // class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
        private static KeyValuePair <string, SyntaxTreeNode> ParseSpan(
            Span span,
            IReadOnlyDictionary <string, string> attributeValueTypes)
        {
            var afterEquals = false;
            var builder     = new SpanBuilder
            {
                CodeGenerator = span.CodeGenerator,
                EditHandler   = span.EditHandler,
                Kind          = span.Kind
            };
            var    htmlSymbols  = span.Symbols.OfType <HtmlSymbol>().ToArray();
            var    symbolOffset = 1;
            string name         = null;

            // Iterate down through the symbols to find the name and the start of the value.
            // We subtract the symbolOffset so we don't accept an ending quote of a span.
            for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++)
            {
                var symbol = htmlSymbols[i];

                if (name == null && symbol.Type == HtmlSymbolType.Text)
                {
                    name = symbol.Content;
                }
                else if (afterEquals)
                {
                    builder.Accept(symbol);
                }
                else if (symbol.Type == HtmlSymbolType.Equals)
                {
                    // We've found an '=' symbol, this means that the coming symbols will either be a quote
                    // or value (in the case that the value is unquoted).
                    // Spaces after/before the equal symbol are not yet supported:
                    // https://github.com/aspnet/Razor/issues/123

                    // TODO: Handle malformed tags, if there's an '=' then there MUST be a value.
                    // https://github.com/aspnet/Razor/issues/104

                    // Check for attribute start values, aka single or double quote
                    if (IsQuote(htmlSymbols[i + 1]))
                    {
                        // Move past the attribute start so we can accept the true value.
                        i++;
                    }
                    else
                    {
                        // Set the symbol offset to 0 so we don't attempt to skip an end quote that doesn't exist.
                        symbolOffset = 0;
                    }

                    afterEquals = true;
                }
            }

            return(CreateMarkupAttribute(name, builder, attributeValueTypes));
        }
        public void GetOrigninalText_SpanIsOwner_ReturnsContent_ZeroLengthSpan()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(13, 0, 0));

            builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
            builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));

            var span = new Span(builder);

            var change = new SourceChange(15, 0, "heyo");

            // Act
            var result = change.GetOriginalText(span);

            // Act
            Assert.Equal(string.Empty, result);
        }
Exemple #15
0
        public static Span BuildSpan(string content, SpanKind kind)
        {
            SpanBuilder builder = new SpanBuilder();

            builder.Kind = kind;
            builder.Accept(new HtmlSymbol(new SourceLocation(), content, HtmlSymbolType.Text));

            return(builder.Build());
        }
        public void GetOffSet_SpanIsOwner_ReturnsOffset()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(13, 0, 0));

            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "Hello, "));
            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "World"));

            var span = new Span(builder);

            var change = new SourceChange(15, 2, "heyo");

            // Act
            var result = change.GetOffset(span);

            // Act
            Assert.Equal(2, result);
        }
Exemple #17
0
        private static void VisitSymbol(ISymbol currentSymbol, ISymbol previousSymbol, SpanBuilder builder)
        {
            if (IsSymbolWhiteSpaceOrNewLine(currentSymbol, out var currentHtmlSymbol))
            {
                if (IsSymbolWhiteSpaceOrNewLine(previousSymbol, out var _))
                {
                    // both current and previous symbols are whitespace/newline, we can skip current symbol
                    return;
                }

                // current symbol is whitespace/newline, previous is not, we'll replace current with the smallest
                var replacementSymbol = GetReplacementSymbol(currentHtmlSymbol);
                builder.Accept(replacementSymbol);
                return;
            }

            builder.Accept(currentSymbol);
        }
        public void GetOffSet_SpanIsNotOwnerOfChange_ThrowsException(int absoluteIndex, int length)
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(13, 0, 0));

            builder.Accept(new RawTextSymbol(new SourceLocation(13, 0, 13), "Hello, "));
            builder.Accept(new RawTextSymbol(new SourceLocation(20, 0, 20), "World"));

            var span = new Span(builder);

            var change = new SourceChange(12, 2, "heyo");

            var expected = $"The node '{span}' is not the owner of change '{change}'.";

            // Act & Assert
            var exception = Assert.Throws <InvalidOperationException>(() => { change.GetOffset(span); });

            Assert.Equal(expected, exception.Message);
        }
Exemple #19
0
 public SpanConstructor(SpanKind kind, IEnumerable<ISymbol> symbols)
 {
     Builder = new SpanBuilder();
     Builder.Kind = kind;
     Builder.EditHandler = SpanEditHandler.CreateDefault(TestTokenizer);
     foreach (ISymbol sym in symbols)
     {
         Builder.Accept(sym);
     }
 }
 public SpanConstructor(SpanKind kind, IEnumerable <ISymbol> symbols)
 {
     Builder             = new SpanBuilder();
     Builder.Kind        = kind;
     Builder.EditHandler = SpanEditHandler.CreateDefault(TestTokenizer);
     foreach (ISymbol sym in symbols)
     {
         Builder.Accept(sym);
     }
 }
        public void GetOffSet_SpanIsNotOwnerOfChange_ThrowsException()
        {
            // Arrange
            var builder = new SpanBuilder(new SourceLocation(13, 0, 0));

            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "Hello, "));
            builder.Accept(SyntaxFactory.Token(SyntaxKind.Unknown, "World"));

            var span = new Span(builder);

            var change = new SourceChange(12, 2, "heyo");

            var expected = $"The node '{span}' is not the owner of change '{change}'.";

            // Act & Assert
            var exception = Assert.Throws <InvalidOperationException>(() => { change.GetOffset(span); });

            Assert.Equal(expected, exception.Message);
        }
Exemple #22
0
        private static Span GetSpan(SourceLocation start, string content)
        {
            var spanBuilder = new SpanBuilder(start);
            var tokens      = CSharpLanguageCharacteristics.Instance.TokenizeString(content).ToArray();

            foreach (var token in tokens)
            {
                spanBuilder.Accept(token);
            }
            var span = spanBuilder.Build();

            return(span);
        }
        public void IsCSharpOpenCurlyBrace_SpanWithLeftBrace_ReturnTrue()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);
            childBuilder.Accept(SyntaxFactory.Token(SyntaxKind.LeftBrace, "{"));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.True(result);
        }
        public void IsCSharpOpenCurlyBrace_SpanWithUnsupportedSymbolType_ReturnFalse(string content, object symbolTypeObject)
        {
            // Arrange
            var symbolType = (SyntaxKind)symbolTypeObject;
            var childBuilder = new SpanBuilder(SourceLocation.Zero);
            childBuilder.Accept(SyntaxFactory.Token(symbolType, content));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
        public void IsCSharpOpenCurlyBrace_SpanWithHtmlSymbol_ReturnFalse()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);

            childBuilder.Accept(new HtmlToken("hello", HtmlTokenType.Text));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.False(result);
        }
Exemple #26
0
        public void IsCSharpOpenCurlyBrace_SpanWithLeftBrace_ReturnTrue()
        {
            // Arrange
            var childBuilder = new SpanBuilder(SourceLocation.Zero);

            childBuilder.Accept(new CSharpSymbol("{", CSharpSymbolType.LeftBrace));
            var child = childBuilder.Build();

            // Act
            var result = DefaultRazorIndentationFactsService.IsCSharpOpenCurlyBrace(child);

            // Assert
            Assert.True(result);
        }
        private static Span CreateCodeSpan(string code)
        {
            // TODO: Correctly reconstruct the syntax tree with correct symbols
            var builder = new SpanBuilder();

            builder.Kind = SpanKind.Code;

            builder.Accept(
                new HtmlSymbol(
                    new SourceLocation(0, 0, 0),
                    code,
                    HtmlSymbolType.Text
                    )
                );

            return(builder.Build());
        }
Exemple #28
0
        protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange)
        {
            var newContent = normalizedChange.ApplyChange(target);
            var newSpan    = new SpanBuilder(target);

            newSpan.ClearSymbols();
            foreach (ISymbol sym in Tokenizer(newContent))
            {
                sym.OffsetStart(target.Start);
                newSpan.Accept(sym);
            }
            if (target.Next != null)
            {
                var newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent);
                target.Next.ChangeStart(newEnd);
            }
            return(newSpan);
        }
Exemple #29
0
        public Span Span(SpanKind kind, params ISymbol[] symbols)
        {
            var builder = new SpanBuilder();

            builder.Kind = kind;

            foreach (var symbol in symbols)
            {
                builder.Accept(symbol);
            }

            var span = builder.Build();

            if (_last != null)
            {
                span.Previous = _last;
                _last.Next    = span;
            }
            _last = span;

            return(span);
        }
        public override void VisitSpan(Span span)
        {
            if (span.Kind == SpanKind.Markup)
            {
                string content = span.Content;

                content = _minifier.Minify(content);

                // We replace the content with the minified markup
                // and then let the CSharp/VB generator do their jobs.
                var builder = new SpanBuilder {
                    CodeGenerator = span.CodeGenerator, EditHandler = span.EditHandler, Kind = span.Kind, Start = span.Start
                };
                var symbol = new MarkupSymbol {
                    Content = content
                };
                builder.Accept(symbol);
                span.ReplaceWith(builder);
            }

            base.VisitSpan(span);
        }
        public void OptimizeBlock(BlockBuilder block)
        {
            Require.NotNull(block, nameof(block));

            for (int i = 0; i < block.Children.Count; i++)
            {
                var span = block.Children[i] as Span;

                if (!IsMarkup(span))
                {
                    // On ne touche pas les éléments qui ne sont pas de type Markup.
                    continue;
                }

                // Si on est déjà arrivé à la dernière position, on récupère le contenu directement.
                string content
                    = i == block.Children.Count - 1
                    ? span.Content
                    : RemoveMarkupAndMergeContentAfter(block, span.Content, ref i);

                if (String.IsNullOrWhiteSpace(content))
                {
                    // Le contenu n'est constitué que d'espaces blancs.
                    block.Children.RemoveAt(i);
                    continue;
                }

                // On optimise le contenu et on recrée l'élément.
                var builder = new SpanBuilder(span);
                builder.ClearSymbols();

                // FIXME: On perd toute information contextuelle.
                builder.Accept(new Symbol_ {
                    Content = BustWhiteSpaces(content)
                });
                span.ReplaceWith(builder);
            }
        }
Exemple #32
0
 protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange)
 {
     var newContent = normalizedChange.ApplyChange(target);
     var newSpan = new SpanBuilder(target);
     newSpan.ClearSymbols();
     foreach (ISymbol sym in Tokenizer(newContent))
     {
         sym.OffsetStart(target.Start);
         newSpan.Accept(sym);
     }
     if (target.Next != null)
     {
         var newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent);
         target.Next.ChangeStart(newEnd);
     }
     return newSpan;
 }
        // This method handles cases when the attribute is a simple span attribute such as
        // class="something moresomething".  This does not handle complex attributes such as
        // class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
        private static TryParseResult TryParseSpan(
            Span span,
            IEnumerable <TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            var afterEquals = false;
            var builder     = new SpanBuilder
            {
                ChunkGenerator = span.ChunkGenerator,
                EditHandler    = span.EditHandler,
                Kind           = span.Kind
            };

            // Will contain symbols that represent a single attribute value: <input| class="btn"| />
            var htmlSymbols = span.Symbols.OfType <HtmlSymbol>().ToArray();
            var capturedAttributeValueStart = false;
            var attributeValueStartLocation = span.Start;

            // Default to DoubleQuotes. We purposefully do not persist NoQuotes ValueStyle to stay consistent with the
            // TryParseBlock() variation of attribute parsing.
            var attributeValueStyle = HtmlAttributeValueStyle.DoubleQuotes;

            // The symbolOffset is initialized to 0 to expect worst case: "class=". If a quote is found later on for
            // the attribute value the symbolOffset is adjusted accordingly.
            var    symbolOffset = 0;
            string name         = null;

            // Iterate down through the symbols to find the name and the start of the value.
            // We subtract the symbolOffset so we don't accept an ending quote of a span.
            for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++)
            {
                var symbol = htmlSymbols[i];

                if (afterEquals)
                {
                    // We've captured all leading whitespace, the attribute name, and an equals with an optional
                    // quote/double quote. We're now at: " asp-for='|...'" or " asp-for=|..."
                    // The goal here is to capture all symbols until the end of the attribute. Note this will not
                    // consume an ending quote due to the symbolOffset.

                    // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the
                    // parent which is why we need to mark our start location prior to adding the symbol.
                    // This is needed to know the location of the attribute value start within the document.
                    if (!capturedAttributeValueStart)
                    {
                        capturedAttributeValueStart = true;

                        attributeValueStartLocation = span.Start + symbol.Start;
                    }

                    builder.Accept(symbol);
                }
                else if (name == null && HtmlMarkupParser.IsValidAttributeNameSymbol(symbol))
                {
                    // We've captured all leading whitespace prior to the attribute name.
                    // 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);

                    // Move the indexer past the attribute name symbols.
                    i += symbolContents.Count() - 1;

                    name = string.Concat(symbolContents);
                    attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, name);
                }
                else if (symbol.Type == HtmlSymbolType.Equals)
                {
                    // We've captured all leading whitespace and the attribute name.
                    // We're now at: " asp-for|='...'" or " asp-for|=..."
                    // The goal here is to consume the equal sign and the optional single/double-quote.

                    // The coming symbols will either be a quote or value (in the case that the value is unquoted).

                    SourceLocation symbolStartLocation;

                    // Skip the whitespace preceding the start of the attribute value.
                    do
                    {
                        i++; // Start from the symbol after '='.
                    } while (i < htmlSymbols.Length &&
                             (htmlSymbols[i].Type == HtmlSymbolType.WhiteSpace ||
                              htmlSymbols[i].Type == HtmlSymbolType.NewLine));

                    // Check for attribute start values, aka single or double quote
                    if (i < htmlSymbols.Length && IsQuote(htmlSymbols[i]))
                    {
                        if (htmlSymbols[i].Type == HtmlSymbolType.SingleQuote)
                        {
                            attributeValueStyle = HtmlAttributeValueStyle.SingleQuotes;
                        }

                        symbolStartLocation = htmlSymbols[i].Start;

                        // If there's a start quote then there must be an end quote to be valid, skip it.
                        symbolOffset = 1;
                    }
                    else
                    {
                        // We are at the symbol after equals. Go back to equals to ensure we don't skip past that symbol.
                        i--;

                        symbolStartLocation = symbol.Start;
                    }

                    attributeValueStartLocation =
                        span.Start +
                        symbolStartLocation +
                        new SourceLocation(absoluteIndex: 1, lineIndex: 0, characterIndex: 1);

                    afterEquals = true;
                }
                else if (symbol.Type == HtmlSymbolType.WhiteSpace)
                {
                    // We're at the start of the attribute, this branch may be hit on the first iterations of
                    // the loop since the parser separates attributes with their spaces included as symbols.
                    // We're at: "| asp-for='...'" or "| asp-for=..."
                    // Note: This will not be hit even for situations like asp-for  ="..." because the core Razor
                    // parser currently does not know how to handle attributes in that format. This will be addressed
                    // by https://github.com/aspnet/Razor/issues/123.

                    attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, symbol.Content);
                }
            }

            // After all symbols have been added we need to set the builders start position so we do not indirectly
            // modify each symbol's Start location.
            builder.Start = attributeValueStartLocation;

            if (name == null)
            {
                // We couldn't find a name, 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"| |
                if (!string.IsNullOrWhiteSpace(span.Content))
                {
                    errorSink.OnError(
                        span.Start,
                        RazorResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed,
                        span.Content.Length);
                }

                return(null);
            }

            var result = CreateTryParseResult(name, descriptors);

            // If we're not after an equal then we should treat the value as if it were a minimized attribute.
            Span attributeValue = null;

            if (afterEquals)
            {
                attributeValue = CreateMarkupAttribute(builder, result.IsBoundNonStringAttribute);
            }
            else
            {
                attributeValueStyle = HtmlAttributeValueStyle.Minimized;
            }

            result.AttributeValueNode  = attributeValue;
            result.AttributeValueStyle = attributeValueStyle;
            return(result);
        }
        // This method handles cases when the attribute is a simple span attribute such as
        // class="something moresomething".  This does not handle complex attributes such as
        // class="@myclass". Therefore the span.Content is equivalent to the entire attribute.
        private static TryParseResult TryParseSpan(
            Span span,
            IEnumerable<TagHelperDescriptor> descriptors,
            ErrorSink errorSink)
        {
            var afterEquals = false;
            var builder = new SpanBuilder
            {
                ChunkGenerator = span.ChunkGenerator,
                EditHandler = span.EditHandler,
                Kind = span.Kind
            };

            // Will contain symbols that represent a single attribute value: <input| class="btn"| />
            var htmlSymbols = span.Symbols.OfType<HtmlSymbol>().ToArray();
            var capturedAttributeValueStart = false;
            var attributeValueStartLocation = span.Start;

            // Default to DoubleQuotes. We purposefully do not persist NoQuotes ValueStyle to stay consistent with the
            // TryParseBlock() variation of attribute parsing.
            var attributeValueStyle = HtmlAttributeValueStyle.DoubleQuotes;

            // The symbolOffset is initialized to 0 to expect worst case: "class=". If a quote is found later on for
            // the attribute value the symbolOffset is adjusted accordingly.
            var symbolOffset = 0;
            string name = null;

            // Iterate down through the symbols to find the name and the start of the value.
            // We subtract the symbolOffset so we don't accept an ending quote of a span.
            for (var i = 0; i < htmlSymbols.Length - symbolOffset; i++)
            {
                var symbol = htmlSymbols[i];

                if (afterEquals)
                {
                    // We've captured all leading whitespace, the attribute name, and an equals with an optional
                    // quote/double quote. We're now at: " asp-for='|...'" or " asp-for=|..."
                    // The goal here is to capture all symbols until the end of the attribute. Note this will not
                    // consume an ending quote due to the symbolOffset.

                    // When symbols are accepted into SpanBuilders, their locations get altered to be offset by the
                    // parent which is why we need to mark our start location prior to adding the symbol.
                    // This is needed to know the location of the attribute value start within the document.
                    if (!capturedAttributeValueStart)
                    {
                        capturedAttributeValueStart = true;

                        attributeValueStartLocation = span.Start + symbol.Start;
                    }

                    builder.Accept(symbol);
                }
                else if (name == null && HtmlMarkupParser.IsValidAttributeNameSymbol(symbol))
                {
                    // We've captured all leading whitespace prior to the attribute name.
                    // 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);

                    // Move the indexer past the attribute name symbols.
                    i += symbolContents.Count() - 1;

                    name = string.Concat(symbolContents);
                    attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, name);
                }
                else if (symbol.Type == HtmlSymbolType.Equals)
                {
                    // We've captured all leading whitespace and the attribute name.
                    // We're now at: " asp-for|='...'" or " asp-for|=..."
                    // The goal here is to consume the equal sign and the optional single/double-quote.

                    // The coming symbols will either be a quote or value (in the case that the value is unquoted).

                    SourceLocation symbolStartLocation;

                    // Skip the whitespace preceding the start of the attribute value.
                    do
                    {
                        i++; // Start from the symbol after '='.
                    } while (i < htmlSymbols.Length &&
                        (htmlSymbols[i].Type == HtmlSymbolType.WhiteSpace ||
                        htmlSymbols[i].Type == HtmlSymbolType.NewLine));

                    // Check for attribute start values, aka single or double quote
                    if (i < htmlSymbols.Length && IsQuote(htmlSymbols[i]))
                    {
                        if (htmlSymbols[i].Type == HtmlSymbolType.SingleQuote)
                        {
                            attributeValueStyle = HtmlAttributeValueStyle.SingleQuotes;
                        }

                        symbolStartLocation = htmlSymbols[i].Start;

                        // If there's a start quote then there must be an end quote to be valid, skip it.
                        symbolOffset = 1;
                    }
                    else
                    {
                        // We are at the symbol after equals. Go back to equals to ensure we don't skip past that symbol.
                        i--;

                        symbolStartLocation = symbol.Start;
                    }

                    attributeValueStartLocation =
                        span.Start +
                        symbolStartLocation +
                        new SourceLocation(absoluteIndex: 1, lineIndex: 0, characterIndex: 1);

                    afterEquals = true;
                }
                else if (symbol.Type == HtmlSymbolType.WhiteSpace)
                {
                    // We're at the start of the attribute, this branch may be hit on the first iterations of
                    // the loop since the parser separates attributes with their spaces included as symbols.
                    // We're at: "| asp-for='...'" or "| asp-for=..."
                    // Note: This will not be hit even for situations like asp-for  ="..." because the core Razor
                    // parser currently does not know how to handle attributes in that format. This will be addressed
                    // by https://github.com/aspnet/Razor/issues/123.

                    attributeValueStartLocation = SourceLocation.Advance(attributeValueStartLocation, symbol.Content);
                }
            }

            // After all symbols have been added we need to set the builders start position so we do not indirectly
            // modify each symbol's Start location.
            builder.Start = attributeValueStartLocation;

            if (name == null)
            {
                // We couldn't find a name, 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"| |
                if (!string.IsNullOrWhiteSpace(span.Content))
                {
                    errorSink.OnError(
                        span.Start,
                        RazorResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed,
                        span.Content.Length);
                }

                return null;
            }

            var result = CreateTryParseResult(name, descriptors);

            // If we're not after an equal then we should treat the value as if it were a minimized attribute.
            Span attributeValue = null;
            if (afterEquals)
            {
                attributeValue = CreateMarkupAttribute(builder, result.IsBoundNonStringAttribute);
            }
            else
            {
                attributeValueStyle = HtmlAttributeValueStyle.Minimized;
            }

            result.AttributeValueNode = attributeValue;
            result.AttributeValueStyle = attributeValueStyle;
            return result;
        }