public override void BuildSpan(SpanBuilder span, SourceLocation start, string content) { foreach (IToken sym in Language.TokenizeString(start, content)) { span.Accept(sym); } }
public void ReplaceWith_NotifiesParentChildHasChanged() { // Arrange var spanBuilder = new SpanBuilder(SourceLocation.Zero); spanBuilder.Accept(SyntaxFactory.Token(SyntaxKind.HtmlTextLiteral, "hello")); var span = spanBuilder.Build(); var blockBuilder = new BlockBuilder() { Type = BlockKindInternal.Markup, }; blockBuilder.Children.Add(span); var block = blockBuilder.Build(); span.Parent = block; var originalBlockLength = block.Length; var newSpanBuilder = new SpanBuilder(SourceLocation.Zero); newSpanBuilder.Accept(SyntaxFactory.Token(SyntaxKind.HtmlTextLiteral, "hi")); // Act span.ReplaceWith(newSpanBuilder); // Assert Assert.Equal(5, originalBlockLength); Assert.Equal(2, block.Length); }
protected void FillSpan(SpanBuilder builder, SourceLocation start, string content) { builder.Kind = SpanKindInternal.Markup; builder.ChunkGenerator = new MarkupChunkGenerator(); foreach (IToken sym in HtmlLanguageCharacteristics.Instance.TokenizeString(start, content)) { builder.Accept(sym); } }
public SpanConstructor(SpanKindInternal kind, SourceLocation location, IEnumerable <IToken> tokens) { Builder = new SpanBuilder(location); Builder.Kind = kind; Builder.EditHandler = SpanEditHandler.CreateDefault((content) => SpanConstructor.TestTokenizer(content)); foreach (IToken sym in tokens) { Builder.Accept(sym); } }
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); }
protected virtual SpanBuilder UpdateSpan(Span target, SourceChange change) { var newContent = change.GetEditedContent(target); var newSpan = new SpanBuilder(target); newSpan.ClearTokens(); foreach (var token in Tokenizer(newContent)) { newSpan.Accept(token); } if (target.Next != null) { var newEnd = SourceLocationTracker.CalculateNewLocation(target.Start, newContent); target.Next.ChangeStart(newEnd); } return(newSpan); }
public void ReplaceWith_ResetsLength() { // Arrange var builder = new SpanBuilder(SourceLocation.Zero); builder.Accept(SyntaxFactory.Token(SyntaxKind.HtmlTextLiteral, "hello")); var span = builder.Build(); var newBuilder = new SpanBuilder(SourceLocation.Zero); newBuilder.Accept(SyntaxFactory.Token(SyntaxKind.HtmlTextLiteral, "hi")); var originalLength = span.Length; // Act span.ReplaceWith(newBuilder); // Assert Assert.Equal(5, originalLength); Assert.Equal(2, span.Length); }
public void ReplaceWith_ResetsLength() { // Arrange var builder = new SpanBuilder(SourceLocation.Zero); builder.Accept(new HtmlToken("hello", HtmlTokenType.Text)); var span = builder.Build(); var newBuilder = new SpanBuilder(SourceLocation.Zero); newBuilder.Accept(new HtmlToken("hi", HtmlTokenType.Text)); var originalLength = span.Length; // Act span.ReplaceWith(newBuilder); // Assert Assert.Equal(5, originalLength); Assert.Equal(2, span.Length); }
public void Clone_ClonesSpan() { // Arrange var spanBuilder = new SpanBuilder(new SourceLocation(1, 2, 3)) { EditHandler = new SpanEditHandler(CSharpLanguageCharacteristics.Instance.TokenizeString), Kind = SpanKindInternal.Transition, ChunkGenerator = new ExpressionChunkGenerator(), }; spanBuilder.Accept(new CSharpSymbol("@", CSharpSymbolType.Transition)); var span = spanBuilder.Build(); // Act var copy = (Span)span.Clone(); // Assert Assert.Equal(span, copy); Assert.NotSame(span, copy); }
public void ChildChanged_NotifiesParent() { // Arrange var spanBuilder = new SpanBuilder(SourceLocation.Zero); spanBuilder.Accept(new HtmlSymbol("hello", HtmlSymbolType.Text)); var span = spanBuilder.Build(); var blockBuilder = new BlockBuilder() { Type = BlockKindInternal.Markup, }; blockBuilder.Children.Add(span); var childBlock = blockBuilder.Build(); blockBuilder = new BlockBuilder() { Type = BlockKindInternal.Markup, }; blockBuilder.Children.Add(childBlock); var parentBlock = blockBuilder.Build(); var originalBlockLength = parentBlock.Length; spanBuilder = new SpanBuilder(SourceLocation.Zero); spanBuilder.Accept(new HtmlSymbol("hi", HtmlSymbolType.Text)); span.ReplaceWith(spanBuilder); // Wire up parents now so we can re-trigger ChildChanged to cause cache refresh. span.Parent = childBlock; childBlock.Parent = parentBlock; // Act childBlock.ChildChanged(); // Assert Assert.Equal(5, originalBlockLength); Assert.Equal(2, parentBlock.Length); }
// 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, HashSet <string> processedBoundAttributeNames) { var afterEquals = false; var builder = new SpanBuilder(span.Start) { 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 = AttributeStructure.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 = 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 nameBuilder = new StringBuilder(); // Move the indexer past the attribute name symbols. for (var j = i; j < htmlSymbols.Length; j++) { var nameSymbol = htmlSymbols[j]; if (!HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol)) { break; } nameBuilder.Append(nameSymbol.Content); i++; } i--; name = nameBuilder.ToString(); attributeValueStartLocation = SourceLocationTracker.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 = AttributeStructure.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 = new SourceLocation( symbolStartLocation.FilePath, symbolStartLocation.AbsoluteIndex + 1, symbolStartLocation.LineIndex, symbolStartLocation.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 = SourceLocationTracker.Advance(attributeValueStartLocation, symbol.Content); } } // After all symbols have been added we need to set the builders start position so we do not indirectly // modify the span'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, LegacyResources.TagHelperBlockRewriter_TagHelperAttributeListMustBeWellFormed, span.Content.Length); } return(null); } var result = CreateTryParseResult(name, descriptors, processedBoundAttributeNames); // 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); } else { attributeValueStyle = AttributeStructure.Minimized; } result.AttributeValueNode = attributeValue; result.AttributeStructure = attributeValueStyle; return(result); }