public override void Accept(Chunk chunk)
        {
            if (chunk is ModelChunk)
            {
                Visit((ModelChunk)chunk);
            }

            base.Accept(chunk);
        }
Example #2
0
        /// <inheritdoc />
        public void VisitChunk(Chunk chunk)
        {
            if (chunk == null)
            {
                throw new ArgumentNullException(nameof(chunk));
            }

            var namespaceChunk = chunk as UsingChunk;
            if (namespaceChunk != null)
            {
                _currentUsings.Add(namespaceChunk.Namespace);
            }
        }
Example #3
0
        /// <inheritdoc />
        public void VisitChunk(Chunk chunk)
        {
            if (chunk == null)
            {
                throw new ArgumentNullException(nameof(chunk));
            }

            var injectChunk = chunk as InjectChunk;
            if (injectChunk != null)
            {
                injectChunk.TypeName = ChunkHelper.ReplaceTModel(injectChunk.TypeName, _modelType);
                _addedMemberNames.Add(injectChunk.MemberName);
            }
        }
        /// <inheritdoc />
        public void VisitChunk(Chunk chunk)
        {
            if (chunk == null)
            {
                throw new ArgumentNullException(nameof(chunk));
            }

            var setBaseTypeChunk = chunk as SetBaseTypeChunk;
            if (setBaseTypeChunk != null)
            {
                setBaseTypeChunk.TypeName = ChunkHelper.ReplaceTModel(setBaseTypeChunk.TypeName, _modelType);
                _isBaseTypeSet = true;
            }
        }
        public void Merge_IgnoresNamespacesThatHaveBeenVisitedInChunkTree()
        {
            // Arrange
            var merger = new UsingChunkMerger();
            var chunkTree = new ChunkTree();
            var inheritedChunks = new Chunk[]
            {
                new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" },
                new InjectChunk("Foo", "Bar")
            };

            // Act
            merger.VisitChunk(new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
            merger.MergeInheritedChunks(chunkTree, inheritedChunks);

            // Assert
            Assert.Empty(chunkTree.Children);
        }
        public void Merge_PicksLastBaseTypeChunkFromChunkTree()
        {
            // Arrange
            var merger = new SetBaseTypeChunkMerger("dynamic");
            var chunkTree = new ChunkTree();
            var inheritedChunks = new Chunk[]
            {
                 new SetBaseTypeChunk { TypeName = "MyBase2" },
                 new LiteralChunk(),
                 new SetBaseTypeChunk { TypeName = "MyBase1" },
            };

            // Act
            merger.MergeInheritedChunks(chunkTree, inheritedChunks);

            // Assert
            var chunk = Assert.Single(chunkTree.Children);
            var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
            Assert.Equal("MyBase1", setBaseTypeChunk.TypeName);
        }
        public void Merge_AddsNamespacesThatHaveNotBeenVisitedInChunkTree()
        {
            // Arrange
            var expected = "MyApp.Models";
            var merger = new UsingChunkMerger();
            var chunkTree = new ChunkTree();
            var inheritedChunks = new Chunk[]
            {
                new UsingChunk { Namespace = expected },
            };

            // Act
            merger.VisitChunk(new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" });
            merger.MergeInheritedChunks(chunkTree, inheritedChunks);

            // Assert
            var chunk = Assert.Single(chunkTree.Children);
            var usingChunk = Assert.IsType<UsingChunk>(chunk);
            Assert.Equal(expected, usingChunk.Namespace);
        }
        public void Merge_DoesNotAddMoreThanOneInstanceOfTheSameInheritedNamespace()
        {
            // Arrange
            var merger = new UsingChunkMerger();
            var chunkTree = new ChunkTree();
            var inheritedChunks = new Chunk[]
            {
                new LiteralChunk(),
                new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" },
                new UsingChunk { Namespace = "Microsoft.AspNet.Mvc" },
                new UsingChunk { Namespace = "Microsoft.AspNet.Mvc.Razor" }
            };

            // Act
            merger.MergeInheritedChunks(chunkTree, inheritedChunks);

            // Assert
            Assert.Equal(2, chunkTree.Children.Count);
            var chunk = Assert.IsType<UsingChunk>(chunkTree.Children[0]);
            Assert.Equal("Microsoft.AspNet.Mvc", chunk.Namespace);
            chunk = Assert.IsType<UsingChunk>(chunkTree.Children[1]);
            Assert.Equal("Microsoft.AspNet.Mvc.Razor", chunk.Namespace);
        }
        private static bool TryGetPlainTextValue(Chunk chunk, out string plainText)
        {
            var parentChunk = chunk as ParentChunk;

            plainText = null;

            if (parentChunk == null || parentChunk.Children.Count != 1)
            {
                return false;
            }

            var literalChildChunk = parentChunk.Children[0] as LiteralChunk;

            if (literalChildChunk == null)
            {
                return false;
            }

            plainText = literalChildChunk.Text;

            return true;
        }
        private static bool IsDynamicAttributeValue(Chunk attributeValueChunk)
        {
            var parentChunk = attributeValueChunk as ParentChunk;
            if (parentChunk != null)
            {
                return parentChunk.Children.Any(child => child is DynamicCodeAttributeChunk);
            }

            return false;
        }
        public void MergeInheritedChunks_MergesDefaultInheritedChunks()
        {
            // Arrange
            var fileProvider = new TestFileProvider();
            fileProvider.AddFile(@"Views\_ViewImports.cshtml",
                               "@inject DifferentHelper<TModel> Html");
            var cache = new DefaultChunkTreeCache(fileProvider);
            var host = new MvcRazorHost(cache);
            var defaultChunks = new Chunk[]
            {
                new InjectChunk("MyTestHtmlHelper", "Html"),
                new UsingChunk { Namespace = "AppNamespace.Model" },
            };
            var inheritedChunkTrees = new ChunkTree[]
            {
                new ChunkTree
                {
                    Chunks = new Chunk[]
                    {
                        new UsingChunk { Namespace = "InheritedNamespace" },
                        new LiteralChunk { Text = "some text" }
                    }
                },
                new ChunkTree
                {
                    Chunks = new Chunk[]
                    {
                        new UsingChunk { Namespace = "AppNamespace.Model" },
                    }
                }
            };

            var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);
            var chunkTree = new ChunkTree();

            // Act
            utility.MergeInheritedChunkTrees(chunkTree,
                                            inheritedChunkTrees,
                                            "dynamic");

            // Assert
            Assert.Equal(3, chunkTree.Chunks.Count);
            Assert.Same(inheritedChunkTrees[0].Chunks[0], chunkTree.Chunks[0]);
            Assert.Same(inheritedChunkTrees[1].Chunks[0], chunkTree.Chunks[1]);
            Assert.Same(defaultChunks[0], chunkTree.Chunks[2]);
        }
        private void RenderUnboundAttribute(string attributeName, Chunk attributeValueChunk)
        {
            // Render children to provide IntelliSense at design time. No need for the execution context logic, it's
            // a runtime feature.
            if (_designTimeMode)
            {
                if (attributeValueChunk != null)
                {
                    _bodyVisitor.Accept(attributeValueChunk);
                }

                return;
            }

            // If we have a minimized attribute there is no value
            if (attributeValueChunk == null)
            {
                _writer
                    .WriteStartInstanceMethodInvocation(
                        ExecutionContextVariableName,
                        _tagHelperContext.ExecutionContextAddMinimizedHtmlAttributeMethodName)
                    .WriteStringLiteral(attributeName)
                    .WriteEndMethodInvocation();
            }
            else
            {
                string textValue = null;
                var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);

                if (isPlainTextValue)
                {
                    // If it's a plain text value then we need to surround the value with quotes.
                    _writer
                        .WriteStartInstanceMethodInvocation(
                            ExecutionContextVariableName,
                            _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
                        .WriteStringLiteral(attributeName)
                        .WriteParameterSeparator()
                        .WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName)
                        .WriteStringLiteral(textValue)
                        .WriteEndMethodInvocation(endLine: false)
                        .WriteEndMethodInvocation();
                }
                else if (IsDynamicAttributeValue(attributeValueChunk))
                {
                    // Dynamic attribute value should be run through the conditional attribute removal system. It's
                    // unbound and contains C#.

                    _writer
                        .WriteStartMethodInvocation(_tagHelperContext.AddHtmlAttributeValuesMethodName)
                        .WriteStringLiteral(attributeName)
                        .WriteParameterSeparator()
                        .Write(ExecutionContextVariableName);

                    _bodyVisitor.Accept(attributeValueChunk);

                    _writer.WriteEndMethodInvocation();
                }
                else
                {
                    // HTML attributes are always strings. This attribute contains C# but is not dynamic. This occurs
                    // when the attribute is a data-* attribute.

                    // Attribute value is not plain text, must be buffered to determine its final value.
                    BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: true);

                    _writer
                        .WriteStartInstanceMethodInvocation(
                            ExecutionContextVariableName,
                            _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
                        .WriteStringLiteral(attributeName)
                        .WriteParameterSeparator()
                        .WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName);

                    RenderBufferedAttributeValueAccessor(_writer);

                    _writer
                        .WriteEndMethodInvocation(endLine: false)
                        .WriteEndMethodInvocation();
                }
            }
        }
        // Render assignment of attribute value to the value accessor.
        private void RenderNewAttributeValueAssignment(
            TagHelperAttributeDescriptor attributeDescriptor,
            bool bufferableAttribute,
            Chunk attributeValueChunk,
            string valueAccessor)
        {
            // Plain text values are non Razor code (@DateTime.Now) values. If an attribute is bufferable it
            // may be more than just a plain text value, it may also contain Razor code which is why we attempt
            // to retrieve a plain text value here.
            string textValue;
            var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);

            if (bufferableAttribute)
            {
                if (!isPlainTextValue)
                {
                    // If we haven't recorded a value and we need to buffer an attribute value and the value is not
                    // plain text then we need to prepare the value prior to setting it below.
                    BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: false);
                }

                _writer.WriteStartAssignment(valueAccessor);

                if (isPlainTextValue)
                {
                    // If the attribute is bufferable but has a plain text value that means the value
                    // is a string which needs to be surrounded in quotes.
                    RenderQuotedAttributeValue(textValue, attributeDescriptor);
                }
                else
                {
                    // The value contains more than plain text e.g. stringAttribute ="Time: @DateTime.Now".
                    RenderBufferedAttributeValue(attributeDescriptor);
                }

                _writer.WriteLine(";");
            }
            else
            {
                // Write out simple assignment for non-string property value. Try to keep the whole
                // statement together and the #line pragma correct to make debugging possible.
                using (var lineMapper = new CSharpLineMappingWriter(
                    _writer,
                    attributeValueChunk.Association.Start,
                    _context.SourceFile))
                {
                    // Place the assignment LHS to align RHS with original attribute value's indentation.
                    // Unfortunately originalIndent is incorrect if original line contains tabs. Unable to
                    // use a CSharpPaddingBuilder because the Association has no Previous node; lost the
                    // original Span sequence when the parse tree was rewritten.
                    var originalIndent = attributeValueChunk.Start.CharacterIndex;
                    var generatedLength = valueAccessor.Length + " = ".Length;
                    var newIndent = originalIndent - generatedLength;
                    if (newIndent > 0)
                    {
                        _writer.Indent(newIndent);
                    }

                    _writer.WriteStartAssignment(valueAccessor);
                    lineMapper.MarkLineMappingStart();

                    // Write out bare expression for this attribute value. Property is not a string.
                    // So quoting or buffering are not helpful.
                    RenderRawAttributeValue(attributeValueChunk, attributeDescriptor, isPlainTextValue);

                    // End the assignment to the attribute.
                    lineMapper.MarkLineMappingEnd();
                    _writer.WriteLine(";");
                }
            }
        }
        private string RenderBoundAttribute(
            string attributeName,
            Chunk attributeValueChunk,
            string tagHelperVariableName,
            string previousValueAccessor,
            TagHelperAttributeDescriptor attributeDescriptor)
        {
            var currentValueAccessor = string.Format(
                CultureInfo.InvariantCulture,
                "{0}.{1}",
                tagHelperVariableName,
                attributeDescriptor.PropertyName);

            if (attributeDescriptor.IsIndexer)
            {
                var dictionaryKey = attributeName.Substring(attributeDescriptor.Name.Length);
                currentValueAccessor += $"[\"{dictionaryKey}\"]";
            }

            // If this attribute value has not been seen before, need to record its value.
            if (previousValueAccessor == null)
            {
                // Bufferable attributes are attributes that can have Razor code inside of them. Such
                // attributes have string values and may be calculated using a temporary TextWriter or other
                // buffer.
                var bufferableAttribute = attributeDescriptor.IsStringProperty;

                RenderNewAttributeValueAssignment(
                    attributeDescriptor,
                    bufferableAttribute,
                    attributeValueChunk,
                    currentValueAccessor);

                if (_designTimeMode)
                {
                    // Execution contexts are a runtime feature.
                    return currentValueAccessor;
                }

                // We need to inform the context of the attribute value.
                _writer
                    .WriteStartInstanceMethodInvocation(
                        ExecutionContextVariableName,
                        _tagHelperContext.ExecutionContextAddTagHelperAttributeMethodName)
                    .WriteStringLiteral(attributeName)
                    .WriteParameterSeparator()
                    .Write(currentValueAccessor)
                    .WriteEndMethodInvocation();

                return currentValueAccessor;
            }
            else
            {
                // The attribute value has already been determined and accessor was passed to us as
                // previousValueAccessor, we don't want to evaluate the value twice so lets just use the
                // previousValueLocation.
                _writer
                    .WriteStartAssignment(currentValueAccessor)
                    .Write(previousValueAccessor)
                    .WriteLine(";");

                return previousValueAccessor;
            }
        }
        private void RenderUnboundAttribute(string attributeName, Chunk attributeValueChunk)
        {
            // Render children to provide IntelliSense at design time. No need for the execution context logic, it's
            // a runtime feature.
            if (_designTimeMode)
            {
                if (attributeValueChunk != null)
                {
                    _bodyVisitor.Accept(attributeValueChunk);
                }

                return;
            }

            // If we have a minimized attribute there is no value
            if (attributeValueChunk == null)
            {
                _writer
                    .WriteStartInstanceMethodInvocation(
                        ExecutionContextVariableName,
                        _tagHelperContext.ExecutionContextAddMinimizedHtmlAttributeMethodName)
                    .WriteStringLiteral(attributeName)
                    .WriteEndMethodInvocation();
            }
            else
            {
                string textValue = null;
                var isPlainTextValue = TryGetPlainTextValue(attributeValueChunk, out textValue);

                if (isPlainTextValue)
                {
                    // If it's a plain text value then we need to surround the value with quotes.
                    _writer
                        .WriteStartInstanceMethodInvocation(
                            ExecutionContextVariableName,
                            _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
                        .WriteStringLiteral(attributeName)
                        .WriteParameterSeparator()
                        .WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName)
                        .WriteStringLiteral(textValue)
                        .WriteEndMethodInvocation(endLine: false)
                        .WriteEndMethodInvocation();
                }
                else if (IsDynamicAttributeValue(attributeValueChunk))
                {
                    // Dynamic attribute value should be run through the conditional attribute removal system. It's
                    // unbound and contains C#.

                    // TagHelper attribute rendering is buffered by default. We do not want to write to the current
                    // writer.
                    var currentTargetWriter = _context.TargetWriterName;
                    var currentWriteAttributeMethodName = _context.Host.GeneratedClassContext.WriteAttributeValueMethodName;
                    _context.TargetWriterName = null;

                    Debug.Assert(attributeValueChunk is ParentChunk);
                    var children = ((ParentChunk)attributeValueChunk).Children;
                    var attributeCount = children.Count(c => c is DynamicCodeAttributeChunk || c is LiteralCodeAttributeChunk);

                    _writer
                        .WriteStartMethodInvocation(_tagHelperContext.BeginAddHtmlAttributeValuesMethodName)
                        .Write(ExecutionContextVariableName)
                        .WriteParameterSeparator()
                        .WriteStringLiteral(attributeName)
                        .WriteParameterSeparator()
                        .Write(attributeCount.ToString(CultureInfo.InvariantCulture))
                        .WriteEndMethodInvocation();

                    _attributeCodeVisitor.Accept(attributeValueChunk);

                    _writer.WriteMethodInvocation(
                        _tagHelperContext.EndAddHtmlAttributeValuesMethodName,
                        ExecutionContextVariableName);

                    _context.TargetWriterName = currentTargetWriter;
                }
                else
                {
                    // HTML attributes are always strings. This attribute contains C# but is not dynamic. This occurs
                    // when the attribute is a data-* attribute.

                    // Attribute value is not plain text, must be buffered to determine its final value.
                    BuildBufferedWritingScope(attributeValueChunk, htmlEncodeValues: true);

                    _writer
                        .WriteStartInstanceMethodInvocation(
                            ExecutionContextVariableName,
                            _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName)
                        .WriteStringLiteral(attributeName)
                        .WriteParameterSeparator()
                        .WriteStartMethodInvocation(_tagHelperContext.MarkAsHtmlEncodedMethodName);

                    RenderBufferedAttributeValueAccessor(_writer);

                    _writer
                        .WriteEndMethodInvocation(endLine: false)
                        .WriteEndMethodInvocation();
                }
            }
        }
        public void GetInheritedChunks_ReturnsEmptySequenceIfNoGlobalsArePresent()
        {
            // Arrange
            var fileProvider = new TestFileProvider();
            fileProvider.AddFile(@"_ViewImports.cs", string.Empty);
            fileProvider.AddFile(@"Views\_Layout.cshtml", string.Empty);
            fileProvider.AddFile(@"Views\home\_not-viewimports.cshtml", string.Empty);
            var cache = new DefaultChunkTreeCache(fileProvider);
            var host = new MvcRazorHost(cache);
            var defaultChunks = new Chunk[]
            {
                new InjectChunk("MyTestHtmlHelper", "Html"),
                new UsingChunk { Namespace = "AppNamespace.Model" },
            };
            var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);

            // Act
            var chunkTrees = utility.GetInheritedChunkTrees(@"Views\home\Index.cshtml");

            // Assert
            Assert.Empty(chunkTrees);
        }
        public void GetInheritedChunks_ReadsChunksFromGlobalFilesInPath()
        {
            // Arrange
            var fileProvider = new TestFileProvider();
            fileProvider.AddFile(@"Views\accounts\_ViewImports.cshtml", "@using AccountModels");
            fileProvider.AddFile(@"Views\Shared\_ViewImports.cshtml", "@inject SharedHelper Shared");
            fileProvider.AddFile(@"Views\home\_ViewImports.cshtml", "@using MyNamespace");
            fileProvider.AddFile(@"Views\_ViewImports.cshtml",
@"@inject MyHelper<TModel> Helper
@inherits MyBaseType

@{
    Layout = ""test.cshtml"";
}

");
            var defaultChunks = new Chunk[]
            {
                new InjectChunk("MyTestHtmlHelper", "Html"),
                new UsingChunk { Namespace = "AppNamespace.Model" },
            };
            var cache = new DefaultChunkTreeCache(fileProvider);
            var host = new MvcRazorHost(cache);
            var utility = new ChunkInheritanceUtility(host, cache, defaultChunks);

            // Act
            var chunkTrees = utility.GetInheritedChunkTrees(@"Views\home\Index.cshtml");

            // Assert
            Assert.Collection(chunkTrees,
                chunkTree =>
                {
                    var viewImportsPath = @"Views\home\_ViewImports.cshtml";
                    Assert.Collection(chunkTree.Chunks,
                        chunk =>
                        {
                            Assert.IsType<LiteralChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            var usingChunk = Assert.IsType<UsingChunk>(chunk);
                            Assert.Equal("MyNamespace", usingChunk.Namespace);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            Assert.IsType<LiteralChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        });
                },
                chunkTree =>
                {
                    var viewImportsPath = @"Views\_ViewImports.cshtml";
                    Assert.Collection(chunkTree.Chunks,
                        chunk =>
                        {
                            Assert.IsType<LiteralChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            var injectChunk = Assert.IsType<InjectChunk>(chunk);
                            Assert.Equal("MyHelper<TModel>", injectChunk.TypeName);
                            Assert.Equal("Helper", injectChunk.MemberName);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            Assert.IsType<LiteralChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            var setBaseTypeChunk = Assert.IsType<SetBaseTypeChunk>(chunk);
                            Assert.Equal("MyBaseType", setBaseTypeChunk.TypeName);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);

                        },
                        chunk =>
                        {
                            Assert.IsType<LiteralChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            Assert.IsType<StatementChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        },
                        chunk =>
                        {
                            Assert.IsType<LiteralChunk>(chunk);
                            Assert.Equal(viewImportsPath, chunk.Start.FilePath);
                        });
                });
        }
 private void RenderRawAttributeValue(
     Chunk attributeValueChunk,
     TagHelperAttributeDescriptor attributeDescriptor,
     bool isPlainTextValue)
 {
     RenderAttributeValue(
         attributeDescriptor,
         valueRenderer: (writer) =>
         {
             var visitor =
                 new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName);
             visitor.Accept(attributeValueChunk);
         },
         complexValue: !isPlainTextValue);
 }
        public void Merge_UsesTheLastInjectChunkOfAPropertyName()
        {
            // Arrange
            var merger = new InjectChunkMerger("dynamic");
            var chunkTree = new ChunkTree();
            var inheritedChunks = new Chunk[]
            {
                new LiteralChunk(),
                new InjectChunk("SomeOtherType", "Property"),
                new InjectChunk("DifferentPropertyType", "DifferentProperty"),
                new InjectChunk("SomeType", "Property"),
            };

            // Act
            merger.MergeInheritedChunks(chunkTree, inheritedChunks);

            // Assert
            Assert.Collection(chunkTree.Children,
                chunk =>
                {
                    var injectChunk = Assert.IsType<InjectChunk>(chunk);
                    Assert.Equal("SomeType", injectChunk.TypeName);
                    Assert.Equal("Property", injectChunk.MemberName);
                },
                chunk =>
                {
                    var injectChunk = Assert.IsType<InjectChunk>(chunk);
                    Assert.Equal("DifferentPropertyType", injectChunk.TypeName);
                    Assert.Equal("DifferentProperty", injectChunk.MemberName);
                });
        }
        // Render a buffered writing scope for the HTML attribute value.
        private void BuildBufferedWritingScope(Chunk htmlAttributeChunk, bool htmlEncodeValues)
        {
            // We're building a writing scope around the provided chunks which captures everything written from the
            // page. Therefore, we do not want to write to any other buffer since we're using the pages buffer to
            // ensure we capture all content that's written, directly or indirectly.
            var oldWriter = _context.TargetWriterName;
            _context.TargetWriterName = null;

            // Need to disable instrumentation inside of writing scopes, the instrumentation will not detect
            // content written inside writing scopes.
            var oldInstrumentation = _context.Host.EnableInstrumentation;

            try
            {
                _context.Host.EnableInstrumentation = false;

                // Scopes are a runtime feature.
                if (!_designTimeMode)
                {
                    _writer.WriteMethodInvocation(_tagHelperContext.StartTagHelperWritingScopeMethodName);
                }

                var visitor = htmlEncodeValues ? _bodyVisitor : _literalBodyVisitor;
                visitor.Accept(htmlAttributeChunk);

                // Scopes are a runtime feature.
                if (!_designTimeMode)
                {
                    _writer.WriteStartAssignment(StringValueBufferVariableName)
                           .WriteMethodInvocation(_tagHelperContext.EndTagHelperWritingScopeMethodName);
                }
            }
            finally
            {
                // Reset instrumentation back to what it was, leaving the writing scope.
                _context.Host.EnableInstrumentation = oldInstrumentation;

                // Reset the writer/buffer back to what it was, leaving the writing scope.
                _context.TargetWriterName = oldWriter;
            }
        }