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 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); } }
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block) { // Collect the content of this node var content = string.Concat(block.Children.Cast<Span>().Select(s => s.Content)); // Create a new span containing this content var span = new SpanBuilder(); span.EditHandler = new SpanEditHandler(HtmlTokenizer.Tokenize); FillSpan(span, block.Children.Cast<Span>().First().Start, content); return span.Build(); }
protected override SyntaxTreeNode RewriteSpan(BlockBuilder parent, Span span) { // Only rewrite if we have a previous that is also markup (CanRewrite does this check for us!) var previous = parent.Children.LastOrDefault() as Span; if (previous == null || !CanRewrite(previous)) { return span; } // Merge spans parent.Children.Remove(previous); var merged = new SpanBuilder(); FillSpan(merged, previous.Start, previous.Content + span.Content); return merged.Build(); }
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; }
private static IBlock BuildParameterInfo(Parameter parameter) { Section result = new Section( new Header(parameter.Documentation.Title ?? parameter.Key)); if (!string.IsNullOrEmpty(parameter.ArgumentTemplate)) { result = result.AddChild(new Paragraph( SpanBuilder.Create() .Write("Regular expression: ") .Italic($"/{parameter.ArgumentTemplate}/"))); } if (!string.IsNullOrEmpty(parameter.Documentation?.Description)) { result = result.AddChild(new Paragraph(parameter.Documentation.Description)); } return(result); }
public override void VisitSpan(Span span) { if (span.Kind == SpanKind.Markup) { var builder = new SpanBuilder { CodeGenerator = span.CodeGenerator, EditHandler = span.EditHandler, Kind = span.Kind, Start = span.Start, }; var symbol = new MarkupSymbol { Content = Minify(span.Content) }; builder.Accept(symbol); span.ReplaceWith(builder); } base.VisitSpan(span); }
public void StartChildSpan_WithSpecifiedSampler() { ISpan rootSpan = SpanBuilder.CreateWithParent(SPAN_NAME, null, spanBuilderOptions) .SetSampler(Samplers.AlwaysSample) .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True(rootSpan.Context.TraceOptions.IsSampled); // Apply the given sampler for child spans. ISpan childSpan = SpanBuilder.CreateWithParent(SPAN_NAME, rootSpan, spanBuilderOptions) .SetSampler(Samplers.NeverSample) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.False(childSpan.Context.TraceOptions.IsSampled); }
public void StartRemoteChildSpan_WithoutSpecifiedSampler() { var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetSampler(Samplers.NeverSample) .SetNoParent() .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True((rootSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) == 0); // Apply default sampler (always true in the tests) for spans with remote parent. var childSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetParent(rootSpan.Context) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.True((childSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) == 0); }
public void ConstructorWithBlockBuilderSetsParent() { // Arrange var builder = new BlockBuilder() { Type = BlockType.Comment }; var span = new SpanBuilder() { Kind = SpanKind.Code }.Build(); builder.Children.Add(span); // Act var block = builder.Build(); // Assert Assert.Same(block, span.Parent); }
public void ConstructorTransfersChildrenFromBlockBuilder() { // Arrange var expected = new SpanBuilder() { Kind = SpanKind.Code }.Build(); var builder = new BlockBuilder() { Type = BlockType.Functions }; builder.Children.Add(expected); // Act var block = builder.Build(); // Assert Assert.Same(expected, block.Children.Single()); }
private static SyntaxTreeNode CreateMarkupAttribute(SpanBuilder builder, bool isBoundNonStringAttribute) { Span value = null; // Builder will be null in the case of minimized attributes if (builder != null) { // If the attribute was requested by a tag helper but the corresponding property was not a string, // then treat its value as code. A non-string value can be any C# value so we need to ensure the // SyntaxTreeNode reflects that. if (isBoundNonStringAttribute) { builder.Kind = SpanKind.Code; } value = builder.Build(); } return(value); }
public void StartChildSpan() { var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetNoParent() .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True(rootSpan.IsRecordingEvents); Assert.True((rootSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0); var childSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetParent(rootSpan) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.Equal(rootSpan.Context.SpanId, ((Span)childSpan).ToSpanData().ParentSpanId); }
public void StartSpanFromCurrentActivity() { var activity = new Activity(SpanName).Start(); activity.TraceStateString = "k1=v1,k2=v2"; var span = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetCreateChild(false) .StartSpan(); Assert.True(span.Context.IsValid); Assert.Equal(activity.TraceId, span.Context.TraceId); Assert.Equal(activity.SpanId, span.Context.SpanId); Assert.True(((Span)span).ParentSpanId == default); Assert.NotNull(Activity.Current); Assert.Equal(Activity.Current, activity); Assert.Equal("k1=v1,k2=v2", span.Context.Tracestate.ToString()); }
public void StartChildSpan() { var rootSpan = new SpanBuilder(SpanName, spanProcessor, alwaysSampleTracerConfiguration, Resource.Empty) .SetSpanKind(SpanKind.Internal) .SetNoParent() .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True(rootSpan.IsRecordingEvents); Assert.True((rootSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0); var childSpan = (Span) new SpanBuilder(SpanName, spanProcessor, alwaysSampleTracerConfiguration, Resource.Empty) .SetSpanKind(SpanKind.Internal) .SetParent(rootSpan) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.Equal(rootSpan.Context.SpanId, childSpan.ParentSpanId); }
public void StartSpanFromCurrentRecordedActivity() { var activity = new Activity(SpanName) .SetIdFormat(ActivityIdFormat.W3C) .Start(); activity.ActivityTraceFlags |= ActivityTraceFlags.Recorded; var span = new SpanBuilder(SpanName, spanProcessor, alwaysSampleTracerConfiguration, Resource.Empty) .SetSpanKind(SpanKind.Internal) .SetCreateChild(false) .StartSpan(); Assert.True(span.Context.IsValid); Assert.Equal(activity.TraceId, span.Context.TraceId); Assert.Equal(activity.SpanId, span.Context.SpanId); Assert.True(((Span)span).ParentSpanId == default); Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0); activity.Stop(); }
public void DateSpecificRetry_CorrectDelayDuration() { var traceId = "123"; var retryDuration = TimeSpan.FromSeconds(10); var retryDurationMs = retryDuration.TotalMilliseconds; var errorMargin = TimeSpan.FromMilliseconds(50).TotalMilliseconds; var spanBatch = SpanBatchBuilder.Create() .WithTraceId(traceId) .WithSpan(SpanBuilder.Create("TestSpan").Build()) .Build(); var config = new TelemetryConfiguration().WithAPIKey("12345"); var dataSender = new SpanDataSender(config); //Mock out the communication layer dataSender.WithHttpHandlerImpl((serializedJson) => { var response = new HttpResponseMessage((System.Net.HttpStatusCode) 429); var retryOnSpecificTime = DateTimeOffset.Now + retryDuration; response.Headers.RetryAfter = new System.Net.Http.Headers.RetryConditionHeaderValue(retryOnSpecificTime); return(Task.FromResult(response)); }); var capturedDelays = new List <int>(); dataSender.WithDelayFunction((delay) => { capturedDelays.Add(delay); return(Task.Delay(0)); }); var response = dataSender.SendDataAsync(spanBatch).Result; Assert.AreEqual(NewRelicResponseStatus.Failure, response.ResponseStatus); Assert.AreEqual(config.MaxRetryAttempts, capturedDelays.Count); Assert.AreEqual(System.Net.HttpStatusCode.RequestTimeout, response.HttpStatusCode); Assert.IsTrue(capturedDelays.All(x => x >= retryDurationMs - errorMargin && x <= retryDurationMs + errorMargin), "Expected duration out of range"); }
protected virtual SpanBuilder UpdateSpan(Span target, TextChange normalizedChange) { string newContent = normalizedChange.ApplyChange(target); SpanBuilder newSpan = new SpanBuilder(target); newSpan.ClearSymbols(); foreach (ISymbol sym in Tokenizer(newContent)) { sym.OffsetStart(target.Start); newSpan.Accept(sym); } if (target.Next != null) { SourceLocation newEnd = SourceLocationTracker.CalculateNewLocation( target.Start, newContent ); target.Next.ChangeStart(newEnd); } return(newSpan); }
public void StartSpanInScopeOfCurrentActivity() { var parentActivity = new Activity(SpanName).Start(); parentActivity.TraceStateString = "k1=v1,k2=v2"; var childSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(parentActivity.TraceId, childSpan.Context.TraceId); Assert.Equal(parentActivity.SpanId, ((Span)childSpan).ParentSpanId); var activity = ((Span)childSpan).Activity; Assert.Equal(parentActivity, Activity.Current); Assert.Equal(activity.Parent, parentActivity); Assert.Equal("k1=v1,k2=v2", childSpan.Context.Tracestate.ToString()); }
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { var spanContext = _spanContextExtractor .Extract(WebOperationContext.Current?.IncomingRequest); spanContext.Sample(_sampler); var spanBuilder = new SpanBuilder(spanContext); spanBuilder.Start() .Tag("action", request.Headers.Action) .Kind(SpanKind.Server) .WithLocalEndpoint(new Endpoint { ServiceName = _localEndpointName }); _spanContextAccessor.SaveContext(spanContext); return(spanBuilder); }
public void StartChildSpan() { var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetNoParent() .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True(rootSpan.IsRecordingEvents); Assert.True(rootSpan.Context.TraceOptions.IsSampled); var childSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(Trace.SpanKind.Internal) .SetParent(rootSpan) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.Equal(rootSpan.Context.SpanId, ((Span)childSpan).ToSpanData().ParentSpanId); Assert.Equal(((Span)rootSpan).TimestampConverter, ((Span)childSpan).TimestampConverter); }
public void SendANonEmptySpanBatch() { var traceId = "123"; var spanBatch = SpanBatchBuilder.Create() .WithTraceId(traceId) .WithSpan(SpanBuilder.Create("TestSpan").Build()) .Build(); var dataSender = new SpanDataSender(new TelemetryConfiguration().WithAPIKey("123456")); dataSender.WithHttpHandlerImpl((serializedJson) => { var response = new HttpResponseMessage(System.Net.HttpStatusCode.OK); return(Task.FromResult(response)); }); var response = dataSender.SendDataAsync(spanBatch).Result; Assert.AreEqual(NewRelicResponseStatus.Success, response.ResponseStatus); }
public void StartSpan_ExplicitNoParent() { var span = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetNoParent() .StartSpan(); Assert.True(span.Context.IsValid); Assert.True(span.IsRecordingEvents); Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0); var spanData = ((Span)span).ToSpanData(); Assert.True(spanData.ParentSpanId == default); var activity = ((Span)span).Activity; Assert.Null(Activity.Current); Assert.Equal(activity.TraceId, span.Context.TraceId); Assert.Equal(activity.SpanId, span.Context.SpanId); Assert.Empty(span.Context.Tracestate.Entries); }
public void StartRemoteSpan() { var spanContext = SpanContext.Create( TraceId.GenerateRandomId(randomHandler), SpanId.GenerateRandomId(randomHandler), TraceOptions.Default, Tracestate.Empty); var span = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetParent(spanContext) .SetRecordEvents(true) .StartSpan(); Assert.True(span.Context.IsValid); Assert.Equal(spanContext.TraceId, span.Context.TraceId); Assert.False(span.Context.TraceOptions.IsSampled); var spanData = ((Span)span).ToSpanData(); Assert.Equal(spanContext.SpanId, spanData.ParentSpanId); }
public void StartRemoteChildSpan_WithSpecifiedSampler() { var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetSampler(Samplers.AlwaysSample) .SetNoParent() .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True(rootSpan.Context.TraceOptions.IsSampled); // Apply given sampler before default sampler for spans with remote parent. var childSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetSampler(Samplers.NeverSample) .SetParent(rootSpan.Context) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.False(childSpan.Context.TraceOptions.IsSampled); }
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block) { // Collect the content of this node 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); Debug.Assert(block.Children.Count > 0); var start = ((Span)block.Children[0]).Start; FillSpan(span, start, builder.ToString()); return(span.Build()); }
public void StartChildSpan_WithSpecifiedSampler() { var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetSampler(Samplers.AlwaysSample) .SetNoParent() .StartSpan(); Assert.True(rootSpan.Context.IsValid); Assert.True((rootSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0); // Apply the given sampler for child spans. var childSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetSampler(Samplers.NeverSample) .SetParent(rootSpan) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.True((childSpan.Context.TraceOptions & ActivityTraceFlags.Recorded) == 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 void StartSpan_WithLinkFromActivity() { var activityLink = new Activity("foo").Start(); activityLink.Stop(); var span = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .AddLink(activityLink) .StartSpan(); var spanData = ((Span)span).ToSpanData(); var links = spanData.Links.Links.ToArray(); Assert.Single(links); Assert.Equal(activityLink.TraceId, links[0].Context.TraceId); Assert.Equal(activityLink.SpanId, links[0].Context.SpanId); Assert.Equal(activityLink.ActivityTraceFlags, links[0].Context.TraceOptions); Assert.Empty(links[0].Context.Tracestate.Entries); Assert.Equal(0, links[0].Attributes.Count); }
public void StartSpanNullParent() { var span = new SpanBuilder(SpanName, spanBuilderOptions) .SetSpanKind(SpanKind.Internal) .SetNoParent() .StartSpan(); Assert.True(span.Context.IsValid); Assert.True(span.IsRecordingEvents); Assert.True((span.Context.TraceOptions & ActivityTraceFlags.Recorded) != 0); var spanData = ((Span)span).ToSpanData(); Assert.True(spanData.ParentSpanId == default); Assert.InRange(spanData.StartTimestamp, PreciseTimestamp.GetUtcNow().AddSeconds(-1), PreciseTimestamp.GetUtcNow().AddSeconds(1)); Assert.Equal(SpanName, spanData.Name); var activity = ((Span)span).Activity; Assert.Null(Activity.Current); Assert.Equal(activity.TraceId, span.Context.TraceId); Assert.Equal(activity.SpanId, span.Context.SpanId); }
public void StartSpan_CurrentSpanParent() { var rootSpan = new SpanBuilder(SpanName, spanBuilderOptions) .SetParent( SpanContext.Create( ActivityTraceId.CreateRandom(), ActivitySpanId.CreateRandom(), ActivityTraceFlags.None, Tracestate.Builder.Set("k1", "v1").Build())) .StartSpan(); using (tracer.WithSpan(rootSpan)) { var childSpan = (Span) new SpanBuilder(SpanName, spanBuilderOptions) .StartSpan(); Assert.True(childSpan.Context.IsValid); Assert.Equal(rootSpan.Context.TraceId, childSpan.Context.TraceId); Assert.Equal(rootSpan.Context.SpanId, childSpan.ParentSpanId); Assert.Equal("k1=v1", childSpan.Context.Tracestate.ToString()); } }
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 HttpRequest([Property(Name = "Request")] HttpRequestMessage request) { var patterns = _options.IgnoredRoutesRegexPatterns; if (patterns == null || patterns.Any(x => Regex.IsMatch(request.RequestUri.AbsolutePath, x))) { return; } var spanBuilder = new SpanBuilder($"httpclient {request.Method} {request.RequestUri.AbsolutePath}"); var spanContext = _tracer.Tracer.GetEntrySpan()?.SpanContext; if (spanContext != null) { spanBuilder.AsChildOf(spanContext); } var span = _tracer.Start(spanBuilder); span.Tags.Client().Component("HttpClient") .HttpMethod(request.Method.Method) .HttpUrl(request.RequestUri.OriginalString) .HttpHost(request.RequestUri.Host) .HttpPath(request.RequestUri.PathAndQuery) .PeerAddress(request.RequestUri.OriginalString) .PeerHostName(request.RequestUri.Host) .PeerPort(request.RequestUri.Port); _tracer.Tracer.Inject(span.SpanContext, request.Headers, (c, k, v) => c.Add(k, v)); span.Log(LogField.CreateNew().ClientSend()); if (request.Method == HttpMethod.Post && _options.TraceHttpContent && request.Content != null) { var result = request.Content.ReadAsStringAsync().Result; if (!string.IsNullOrWhiteSpace(result)) { span.Tags.Add("http.request", _tbbrRegex.Replace(result, "")); } } _tracer.Tracer.SetExitSpan(span); }
public void Double(double a, double b, string format) { var value = a / b; var expected = value.ToString(format, CultureInfo.InvariantCulture); { Span <char> span = stackalloc char[500]; var builder = new SpanBuilder(span); builder.Append(value); Assert.Equal(expected, builder.ToString()); } { Span <char> span = stackalloc char[500]; var builder = new SpanBuilder(span); Assert.True(builder.TryAppend(value)); Assert.Equal(expected, builder.ToString()); } { Span <char> span = stackalloc char[expected.Length - 1]; var builder = new SpanBuilder(span); Assert.False(builder.TryAppend(value)); } }
void Simplify(ref SpanBuilder builder, string source) { var currentCulture = CultureInfo.CurrentCulture; var src = source.AsSpan(); var isDelimiter = false; var srcLength = src.Length; var lastIndex = srcLength - 1; for (var i = 0; i < srcLength; ++i) { var ch = currentCulture.TextInfo.ToLower(src[i]); if (_map.TryGetValue(ch, out var replacement)) { builder.Append(replacement); isDelimiter = false; } else { if (!IsSimple(ch)) { ch = Delimiter; } if (Delimiter == ch) { if (builder.Length != 0 && i < lastIndex && !isDelimiter) { builder.Append(ch); isDelimiter = true; } } else { builder.Append(ch); isDelimiter = false; } } } }
protected override SyntaxTreeNode RewriteBlock(BlockBuilder parent, Block block) { var newBlock = new BlockBuilder(block); newBlock.Children.Clear(); var ws = block.Children.FirstOrDefault() as Span; IEnumerable<SyntaxTreeNode> newNodes = block.Children; if (ws.Content.All(Char.IsWhiteSpace)) { // Add this node to the parent var builder = new SpanBuilder(ws); builder.ClearSymbols(); FillSpan(builder, ws.Start, ws.Content); parent.Children.Add(builder.Build()); // Remove the old whitespace node newNodes = block.Children.Skip(1); } foreach (SyntaxTreeNode node in newNodes) { newBlock.Children.Add(node); } return newBlock.Build(); }
public Span(SpanBuilder builder) { ReplaceWith(builder); }
public abstract void BuildSpan(SpanBuilder span, SourceLocation start, string content);
public override void BuildSpan(SpanBuilder span, SourceLocation start, string content) { throw new NotImplementedException(); }
public void ReplaceWith(SpanBuilder builder) { Kind = builder.Kind; Symbols = builder.Symbols; EditHandler = builder.EditHandler; ChunkGenerator = builder.ChunkGenerator ?? SpanChunkGenerator.Null; _start = builder.Start; _content = null; // Since we took references to the values in SpanBuilder, clear its references out builder.Reset(); }
protected void FillSpan(SpanBuilder builder, SourceLocation start, string content) { _markupSpanFactory(builder, start, content); }
private static Span CreateMarkupAttribute(SpanBuilder builder, bool isBoundNonStringAttribute) { Debug.Assert(builder != null); // If the attribute was requested by a tag helper but the corresponding property was not a string, // then treat its value as code. A non-string value can be any C# value so we need to ensure the // SyntaxTreeNode reflects that. if (isBoundNonStringAttribute) { builder.Kind = SpanKind.Code; } return builder.Build(); }
private static Block RebuildChunkGenerators(Block block, bool isBound) { var builder = new BlockBuilder(block); // Don't want to rebuild unbound dynamic attributes. They need to run through the conditional attribute // removal system at runtime. A conditional attribute at the parse tree rewriting level is defined by // having at least 1 child with a DynamicAttributeBlockChunkGenerator. if (!isBound && block.Children.Any( child => child.IsBlock && ((Block)child).ChunkGenerator is DynamicAttributeBlockChunkGenerator)) { // The parent chunk generator must be removed because it's normally responsible for conditionally // generating the attribute prefix (class=") and suffix ("). The prefix and suffix concepts aren't // applicable for the TagHelper use case since the attributes are put into a dictionary like object as // name value pairs. builder.ChunkGenerator = ParentChunkGenerator.Null; return builder.Build(); } var isDynamic = builder.ChunkGenerator is DynamicAttributeBlockChunkGenerator; // We don't want any attribute specific logic here, null out the block chunk generator. if (isDynamic || builder.ChunkGenerator is AttributeBlockChunkGenerator) { builder.ChunkGenerator = ParentChunkGenerator.Null; } for (var i = 0; i < builder.Children.Count; i++) { var child = builder.Children[i]; if (child.IsBlock) { // The child is a block, recurse down into the block to rebuild its children builder.Children[i] = RebuildChunkGenerators((Block)child, isBound); } else { var childSpan = (Span)child; ISpanChunkGenerator newChunkGenerator = null; var literalGenerator = childSpan.ChunkGenerator as LiteralAttributeChunkGenerator; if (literalGenerator != null) { if (literalGenerator.ValueGenerator == null || literalGenerator.ValueGenerator.Value == null) { newChunkGenerator = new MarkupChunkGenerator(); } else { newChunkGenerator = literalGenerator.ValueGenerator.Value; } } else if (isDynamic && childSpan.ChunkGenerator == SpanChunkGenerator.Null) { // Usually the dynamic chunk generator handles creating the null chunk generators underneath // it. This doesn't make sense in terms of tag helpers though, we need to change null code // generators to markup chunk generators. newChunkGenerator = new MarkupChunkGenerator(); } // If we have a new chunk generator we'll need to re-build the child if (newChunkGenerator != null) { var childSpanBuilder = new SpanBuilder(childSpan) { ChunkGenerator = newChunkGenerator }; builder.Children[i] = childSpanBuilder.Build(); } } } return builder.Build(); }
private static TryParseResult TryParseBlock( string tagName, Block block, IEnumerable<TagHelperDescriptor> descriptors, ErrorSink errorSink) { // TODO: Accept more than just spans: https://github.com/aspnet/Razor/issues/96. // The first child will only ever NOT be a Span if a user is doing something like: // <input @checked /> var childSpan = block.Children.First() as Span; if (childSpan == null || childSpan.Kind != SpanKind.Markup) { errorSink.OnError( block.Start, RazorResources.FormatTagHelpers_CannotHaveCSharpInTagDeclaration(tagName), block.Length); return null; } var builder = new BlockBuilder(block); // If there's only 1 child it means that it's plain text inside of the attribute. // i.e. <div class="plain text in attribute"> if (builder.Children.Count == 1) { return TryParseSpan(childSpan, descriptors, errorSink); } var nameSymbols = childSpan .Symbols .OfType<HtmlSymbol>() .SkipWhile(symbol => !HtmlMarkupParser.IsValidAttributeNameSymbol(symbol)) // Skip prefix .TakeWhile(nameSymbol => HtmlMarkupParser.IsValidAttributeNameSymbol(nameSymbol)) .Select(nameSymbol => nameSymbol.Content); var name = string.Concat(nameSymbols); if (string.IsNullOrEmpty(name)) { errorSink.OnError( childSpan.Start, RazorResources.FormatTagHelpers_AttributesMustHaveAName(tagName), childSpan.Length); return null; } // Have a name now. Able to determine correct isBoundNonStringAttribute value. var result = CreateTryParseResult(name, descriptors); var firstChild = builder.Children[0] as Span; if (firstChild != null && firstChild.Symbols[0] is HtmlSymbol) { var htmlSymbol = firstChild.Symbols[firstChild.Symbols.Count - 1] as HtmlSymbol; switch (htmlSymbol.Type) { // Treat NoQuotes and DoubleQuotes equivalently. We purposefully do not persist NoQuotes // ValueStyles at code generation time to protect users from rendering dynamic content with spaces // that can break attributes. // Ex: <tag my-attribute=@value /> where @value results in the test "hello world". // This way, the above code would render <tag my-attribute="hello world" />. case HtmlSymbolType.Equals: case HtmlSymbolType.DoubleQuote: result.AttributeValueStyle = HtmlAttributeValueStyle.DoubleQuotes; break; case HtmlSymbolType.SingleQuote: result.AttributeValueStyle = HtmlAttributeValueStyle.SingleQuotes; break; default: result.AttributeValueStyle = HtmlAttributeValueStyle.Minimized; break; } } // Remove first child i.e. foo=" builder.Children.RemoveAt(0); // Grabbing last child to check if the attribute value is quoted. var endNode = block.Children.Last(); if (!endNode.IsBlock) { var endSpan = (Span)endNode; // In some malformed cases e.g. <p bar="false', the last Span (false' in the ex.) may contain more // than a single HTML symbol. Do not ignore those other symbols. var symbolCount = endSpan.Symbols.Count(); var endSymbol = symbolCount == 1 ? (HtmlSymbol)endSpan.Symbols.First() : null; // Checking to see if it's a quoted attribute, if so we should remove end quote if (endSymbol != null && IsQuote(endSymbol)) { builder.Children.RemoveAt(builder.Children.Count - 1); } } // We need to rebuild the chunk generators of the builder and its children (this is needed to // ensure we don't do special attribute chunk generation since this is a tag helper). block = RebuildChunkGenerators(builder.Build(), result.IsBoundAttribute); // If there's only 1 child at this point its value could be a simple markup span (treated differently than // block level elements for attributes). if (block.Children.Count() == 1) { var child = block.Children.First() as Span; if (child != null) { // After pulling apart the block we just have a value span. var spanBuilder = new SpanBuilder(child); result.AttributeValueNode = CreateMarkupAttribute(spanBuilder, result.IsBoundNonStringAttribute); return result; } } var isFirstSpan = true; result.AttributeValueNode = ConvertToMarkupAttributeBlock( block, (parentBlock, span) => { // If the attribute was requested by a tag helper but the corresponding property was not a // string, then treat its value as code. A non-string value can be any C# value so we need // to ensure the SyntaxTreeNode reflects that. if (result.IsBoundNonStringAttribute) { // For bound non-string attributes, we'll only allow a transition span to appear at the very // beginning of the attribute expression. All later transitions would appear as code so that // they are part of the generated output. E.g. // key="@value" -> MyTagHelper.key = value // key=" @value" -> MyTagHelper.key = @value // key="1 + @case" -> MyTagHelper.key = 1 + @case // key="@int + @case" -> MyTagHelper.key = int + @case // key="@(a + b) -> MyTagHelper.key = a + b // key="4 + @(a + b)" -> MyTagHelper.key = 4 + @(a + b) if (isFirstSpan && span.Kind == SpanKind.Transition) { // do nothing. } else { var spanBuilder = new SpanBuilder(span); if (parentBlock.Type == BlockType.Expression && (spanBuilder.Kind == SpanKind.Transition || spanBuilder.Kind == SpanKind.MetaCode)) { // Change to a MarkupChunkGenerator so that the '@' \ parenthesis is generated as part of the output. spanBuilder.ChunkGenerator = new MarkupChunkGenerator(); } spanBuilder.Kind = SpanKind.Code; span = spanBuilder.Build(); } } isFirstSpan = false; return span; }); 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; }
public void Change(Action<SpanBuilder> changes) { var builder = new SpanBuilder(this); changes(builder); ReplaceWith(builder); }