/// <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); } }
public void Merge_IgnoresNamespacesThatHaveBeenVisitedInChunkTree() { // Arrange var merger = new UsingChunkMerger(); var chunkTree = new ChunkTree(); var inheritedChunks = new Chunk[] { new UsingChunk { Namespace = "Microsoft.AspNetCore.Mvc" }, new InjectChunk("Foo", "Bar") }; // Act merger.VisitChunk(new UsingChunk { Namespace = "Microsoft.AspNetCore.Mvc" }); merger.MergeInheritedChunks(chunkTree, inheritedChunks); // Assert Assert.Empty(chunkTree.Children); }
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.AspNetCore.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_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_DoesNotAddMoreThanOneInstanceOfTheSameInheritedNamespace() { // Arrange var merger = new UsingChunkMerger(); var chunkTree = new ChunkTree(); var inheritedChunks = new Chunk[] { new LiteralChunk(), new UsingChunk { Namespace = "Microsoft.AspNetCore.Mvc" }, new UsingChunk { Namespace = "Microsoft.AspNetCore.Mvc" }, new UsingChunk { Namespace = "Microsoft.AspNetCore.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.AspNetCore.Mvc", chunk.Namespace); chunk = Assert.IsType<UsingChunk>(chunkTree.Children[1]); Assert.Equal("Microsoft.AspNetCore.Mvc.Razor", chunk.Namespace); }
public static bool IsDynamicAttributeValue(Chunk attributeValueChunk) { var parentChunk = attributeValueChunk as ParentChunk; if (parentChunk != null) { return parentChunk.Children.Any(child => child is DynamicCodeAttributeChunk); } return false; }
// 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.BeginWriteTagHelperAttributeMethodName); } var visitor = htmlEncodeValues ? _bodyVisitor : _literalBodyVisitor; visitor.Accept(htmlAttributeChunk); // Scopes are a runtime feature. if (!_designTimeMode) { _writer .WriteStartAssignment(StringValueBufferVariableName) .WriteMethodInvocation(_tagHelperContext.EndWriteTagHelperAttributeMethodName); } } 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; } }
private void RenderCodeAttributeValue( Chunk attributeValueChunk, TagHelperAttributeDescriptor attributeDescriptor, bool isPlainTextValue) { RenderAttributeValue( attributeDescriptor, valueRenderer: (writer) => { if (attributeDescriptor.IsEnum && isPlainTextValue) { writer .Write("global::") .Write(attributeDescriptor.TypeName) .Write("."); } var visitor = new CSharpTagHelperAttributeValueVisitor(writer, _context, attributeDescriptor.TypeName); visitor.Accept(attributeValueChunk); }, complexValue: !isPlainTextValue); }
// 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.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 code expression for this attribute value. Property is not a string. // So quoting or buffering are not helpful. RenderCodeAttributeValue(attributeValueChunk, attributeDescriptor, isPlainTextValue); // End the assignment to the attribute. lineMapper.MarkLineMappingEnd(); _writer.WriteLine(";"); } } }
public static bool TryGetPlainTextValue(Chunk chunk, out string plainText) { var parentChunk = chunk as ParentChunk; plainText = null; if (parentChunk == null || parentChunk.Children.Count != 1) { return false; } LiteralChunk literalChildChunk; if ((literalChildChunk = parentChunk.Children[0] as LiteralChunk) != null) { plainText = literalChildChunk.Text; return true; } ParentLiteralChunk parentLiteralChunk; if ((parentLiteralChunk = parentChunk.Children[0] as ParentLiteralChunk) != null) { plainText = parentLiteralChunk.GetText(); return true; } return false; }
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 if (attributeValueChunk is PreallocatedTagHelperAttributeChunk) { _writer .WriteStartInstanceMethodInvocation( ExecutionContextVariableName, _tagHelperContext.ExecutionContextAddHtmlAttributeMethodName) .Write(((PreallocatedTagHelperAttributeChunk)attributeValueChunk).AttributeVariableAccessor) .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(); } } }
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; } }
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); }); }
public void MergeInheritedChunks_MergesDefaultInheritedChunks() { // Arrange var fileProvider = new TestFileProvider(); fileProvider.AddFile(@"/Views/_ViewImports.cshtml", "@inject DifferentHelper<TModel> Html"); var cache = new DefaultChunkTreeCache(fileProvider); using (var host = new MvcRazorHost(cache, new TagHelperDescriptorResolver(designTime: false))) { var defaultChunks = new Chunk[] { new InjectChunk("MyTestHtmlHelper", "Html"), new UsingChunk { Namespace = "AppNamespace.Model" }, }; var inheritedChunkTrees = new ChunkTree[] { new ChunkTree { Children = new Chunk[] { new UsingChunk { Namespace = "InheritedNamespace" }, new LiteralChunk { Text = "some text" } } }, new ChunkTree { Children = 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.Collection(chunkTree.Children, chunk => Assert.Same(defaultChunks[1], chunk), chunk => Assert.Same(inheritedChunkTrees[0].Children[0], chunk), chunk => Assert.Same(defaultChunks[0], chunk)); } }
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); using (var host = new MvcRazorHost(cache, new TagHelperDescriptorResolver(designTime: false))) { var utility = new ChunkInheritanceUtility(host, cache, defaultChunks); // Act var chunkTreeResults = utility.GetInheritedChunkTreeResults( PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); // Assert Assert.Collection(chunkTreeResults, chunkTreeResult => { var viewImportsPath = @"/Views/_ViewImports.cshtml"; Assert.Collection(chunkTreeResult.ChunkTree.Children, 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); }); Assert.Equal(viewImportsPath, chunkTreeResult.FilePath); }, chunkTreeResult => { var viewImportsPath = "/Views/home/_ViewImports.cshtml"; Assert.Collection(chunkTreeResult.ChunkTree.Children, 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); }); Assert.Equal(viewImportsPath, chunkTreeResult.FilePath); }); } }
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); using (var host = new MvcRazorHost(cache, new TagHelperDescriptorResolver(designTime: false))) { var defaultChunks = new Chunk[] { new InjectChunk("MyTestHtmlHelper", "Html"), new UsingChunk { Namespace = "AppNamespace.Model" }, }; var utility = new ChunkInheritanceUtility(host, cache, defaultChunks); // Act var chunkTrees = utility.GetInheritedChunkTreeResults(PlatformNormalizer.NormalizePath(@"Views\home\Index.cshtml")); // Assert Assert.Empty(chunkTrees); } }