Add RazorIRNodeReference for manipulating nodes

This commit is contained in:
Ryan Nowak 2017-06-08 20:59:28 -07:00
parent 1fb3c8c26f
commit 6ce71c24e5
10 changed files with 954 additions and 80 deletions

View File

@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
internal static class ViewComponentResources
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.CodeAnalysis.Razor.ViewComponentResources", typeof(ViewComponentResources).GetTypeInfo().Assembly);
= new ResourceManager("Microsoft.AspNetCore.Mvc.Razor.Extensions.ViewComponentResources", typeof(ViewComponentResources).GetTypeInfo().Assembly);
/// <summary>
/// View component '{0}' must have exactly one public method named '{1}' or '{2}'.

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@ -9,7 +8,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Microsoft.AspNetCore.Razor.Language.Legacy;
using Microsoft.CodeAnalysis.Razor;
namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
@ -37,9 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
}
}
foreach (var (node, parent) in visitor.CreateTagHelpers)
foreach (var (parent, node) in visitor.CreateTagHelpers)
{
RewriteCreateNode(visitor.Namespace, visitor.Class, node, parent);
RewriteCreateNode(visitor.Namespace, visitor.Class, (CreateTagHelperIRNode)node, parent);
}
}
@ -230,7 +228,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
public NamespaceDeclarationIRNode Namespace { get; private set; }
public List<(CreateTagHelperIRNode node, RazorIRNode parent)> CreateTagHelpers { get; } = new List<(CreateTagHelperIRNode node, RazorIRNode parent)>();
public List<RazorIRNodeReference> CreateTagHelpers { get; } = new List<RazorIRNodeReference>();
public Dictionary<string, TagHelperDescriptor> TagHelpers { get; } = new Dictionary<string, TagHelperDescriptor>();
@ -243,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
var vcName = tagHelper.Metadata[ViewComponentTagHelperDescriptorConventions.ViewComponentNameKey];
TagHelpers[vcName] = tagHelper;
CreateTagHelpers.Add((node, Parent));
CreateTagHelpers.Add(new RazorIRNodeReference(Parent, node));
}
}

View File

@ -16,25 +16,28 @@ namespace Microsoft.AspNetCore.Razor.Language
var parserOptions = irDocument.Options;
var designTime = parserOptions.DesignTime;
var walker = new DirectiveWalker();
walker.VisitDocument(irDocument);
var classNode = walker.ClassNode;
foreach (var (node, parent) in walker.FunctionsDirectiveNodes)
var classNode = irDocument.FindPrimaryClass();
if (classNode == null)
{
parent.Children.Remove(node);
return;
}
foreach (var child in node.Children.Except(node.Tokens))
foreach (var functions in irDocument.FindDirectiveReferences(CSharpCodeParser.FunctionsDirectiveDescriptor))
{
functions.Remove();
for (var i =0; i < functions.Node.Children.Count; i++)
{
classNode.Children.Add(child);
classNode.Children.Add(functions.Node.Children[i]);
}
}
foreach (var (node, parent) in walker.InheritsDirectiveNodes.Reverse())
foreach (var inherits in irDocument.FindDirectiveReferences(CSharpCodeParser.InheritsDirectiveDescriptor).Reverse())
{
parent.Children.Remove(node);
inherits.Remove();
var token = node.Tokens.FirstOrDefault();
var token = ((DirectiveIRNode)inherits.Node).Tokens.FirstOrDefault();
if (token != null)
{
classNode.BaseType = token.Content;
@ -42,74 +45,30 @@ namespace Microsoft.AspNetCore.Razor.Language
}
}
foreach (var (node, parent) in walker.SectionDirectiveNodes)
foreach (var section in irDocument.FindDirectiveReferences(CSharpCodeParser.SectionDirectiveDescriptor))
{
var sectionIndex = parent.Children.IndexOf(node);
parent.Children.Remove(node);
var defineSectionEndStatement = new CSharpCodeIRNode();
RazorIRBuilder.Create(defineSectionEndStatement)
.Add(new RazorIRToken()
{
Kind = RazorIRToken.TokenKind.CSharp,
Content = "});"
});
parent.Children.Insert(sectionIndex, defineSectionEndStatement);
foreach (var child in node.Children.Except(node.Tokens).Reverse())
{
parent.Children.Insert(sectionIndex, child);
}
var lambdaContent = designTime ? "__razor_section_writer" : string.Empty;
var sectionName = node.Tokens.FirstOrDefault()?.Content;
var defineSectionStartStatement = new CSharpCodeIRNode();
RazorIRBuilder.Create(defineSectionStartStatement)
.Add(new RazorIRToken()
{
Kind = RazorIRToken.TokenKind.CSharp,
Content = $"DefineSection(\"{sectionName}\", async ({lambdaContent}) => {{"
});
var sectionName = ((DirectiveIRNode)section.Node).Tokens.FirstOrDefault()?.Content;
parent.Children.Insert(sectionIndex, defineSectionStartStatement);
}
}
private class DirectiveWalker : RazorIRNodeWalker
{
public ClassDeclarationIRNode ClassNode { get; private set; }
public IList<(DirectiveIRNode node, RazorIRNode parent)> FunctionsDirectiveNodes { get; } = new List<(DirectiveIRNode node, RazorIRNode parent)>();
public IList<(DirectiveIRNode node, RazorIRNode parent)> InheritsDirectiveNodes { get; } = new List<(DirectiveIRNode node, RazorIRNode parent)>();
public IList<(DirectiveIRNode node, RazorIRNode parent)> SectionDirectiveNodes { get; } = new List<(DirectiveIRNode node, RazorIRNode parent)>();
public override void VisitClassDeclaration(ClassDeclarationIRNode node)
{
if (ClassNode == null)
var builder = RazorIRBuilder.Create(new CSharpCodeIRNode());
builder.Add(new RazorIRToken()
{
ClassNode = node;
}
Kind = RazorIRToken.TokenKind.CSharp,
Content = $"DefineSection(\"{sectionName}\", async ({lambdaContent}) => {{"
});
section.InsertBefore(builder.Build());
section.InsertBefore(section.Node.Children.Except(((DirectiveIRNode)section.Node).Tokens));
VisitDefault(node);
}
builder = RazorIRBuilder.Create(new CSharpCodeIRNode());
builder.Add(new RazorIRToken()
{
Kind = RazorIRToken.TokenKind.CSharp,
Content = "});"
});
section.InsertAfter(builder.Build());
public override void VisitDirective(DirectiveIRNode node)
{
if (string.Equals(node.Name, CSharpCodeParser.FunctionsDirectiveDescriptor.Directive, StringComparison.Ordinal))
{
FunctionsDirectiveNodes.Add((node, Parent));
}
else if (string.Equals(node.Name, CSharpCodeParser.InheritsDirectiveDescriptor.Directive, StringComparison.Ordinal))
{
InheritsDirectiveNodes.Add((node, Parent));
}
else if (string.Equals(node.Name, CSharpCodeParser.SectionDirectiveDescriptor.Directive, StringComparison.Ordinal))
{
SectionDirectiveNodes.Add((node, Parent));
}
section.Remove();
}
}
}

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
@ -37,6 +38,23 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
return FindWithAnnotation<NamespaceDeclarationIRNode>(node, CommonAnnotations.PrimaryNamespace);
}
public static IReadOnlyList<RazorIRNodeReference> FindDirectiveReferences(this DocumentIRNode node, DirectiveDescriptor directive)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
if (directive == null)
{
throw new ArgumentNullException(nameof(directive));
}
var visitor = new DirectiveVisitor(directive);
visitor.Visit(node);
return visitor.Directives;
}
private static T FindWithAnnotation<T>(RazorIRNode node, object annotation) where T : RazorIRNode
{
if (node is T target && object.ReferenceEquals(target.Annotations[annotation], annotation))
@ -55,5 +73,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
return null;
}
private class DirectiveVisitor : RazorIRNodeWalker
{
private readonly DirectiveDescriptor _directive;
public DirectiveVisitor(DirectiveDescriptor directive)
{
_directive = directive;
}
public List<RazorIRNodeReference> Directives = new List<RazorIRNodeReference>();
public override void VisitDirective(DirectiveIRNode node)
{
if (_directive == node.Descriptor)
{
Directives.Add(new RazorIRNodeReference(Parent, node));
}
base.VisitDirective(node);
}
}
}
}

View File

@ -0,0 +1,209 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public struct RazorIRNodeReference
{
public RazorIRNodeReference(RazorIRNode parent, RazorIRNode node)
{
if (parent == null)
{
throw new ArgumentNullException(nameof(parent));
}
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
Parent = parent;
Node = node;
}
public void Deconstruct(out RazorIRNode parent, out RazorIRNode node)
{
parent = Parent;
node = Node;
}
public RazorIRNode Node { get; }
public RazorIRNode Parent { get; }
public RazorIRNodeReference InsertAfter(RazorIRNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
if (Parent == null)
{
throw new InvalidOperationException(Resources.IRNodeReference_NotInitialized);
}
if (Parent.Children.IsReadOnly)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_CollectionIsReadOnly(Parent));
}
var index = Parent.Children.IndexOf(Node);
if (index == -1)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_NodeNotFound(
Node,
Parent));
}
Parent.Children.Insert(index + 1, node);
return new RazorIRNodeReference(Parent, node);
}
public void InsertAfter(IEnumerable<RazorIRNode> nodes)
{
if (nodes == null)
{
throw new ArgumentNullException(nameof(nodes));
}
if (Parent == null)
{
throw new InvalidOperationException(Resources.IRNodeReference_NotInitialized);
}
if (Parent.Children.IsReadOnly)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_CollectionIsReadOnly(Parent));
}
var index = Parent.Children.IndexOf(Node);
if (index == -1)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_NodeNotFound(
Node,
Parent));
}
foreach (var node in nodes)
{
Parent.Children.Insert(++index, node);
}
}
public RazorIRNodeReference InsertBefore(RazorIRNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
if (Parent == null)
{
throw new InvalidOperationException(Resources.IRNodeReference_NotInitialized);
}
if (Parent.Children.IsReadOnly)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_CollectionIsReadOnly(Parent));
}
var index = Parent.Children.IndexOf(Node);
if (index == -1)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_NodeNotFound(
Node,
Parent));
}
Parent.Children.Insert(index, node);
return new RazorIRNodeReference(Parent, node);
}
public void InsertBefore(IEnumerable<RazorIRNode> nodes)
{
if (nodes == null)
{
throw new ArgumentNullException(nameof(nodes));
}
if (Parent == null)
{
throw new InvalidOperationException(Resources.IRNodeReference_NotInitialized);
}
if (Parent.Children.IsReadOnly)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_CollectionIsReadOnly(Parent));
}
var index = Parent.Children.IndexOf(Node);
if (index == -1)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_NodeNotFound(
Node,
Parent));
}
foreach (var node in nodes)
{
Parent.Children.Insert(index++, node);
}
}
public void Remove()
{
if (Parent == null)
{
throw new InvalidOperationException(Resources.IRNodeReference_NotInitialized);
}
if (Parent.Children.IsReadOnly)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_CollectionIsReadOnly(Parent));
}
var index = Parent.Children.IndexOf(Node);
if (index == -1)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_NodeNotFound(
Node,
Parent));
}
Parent.Children.RemoveAt(index);
}
public RazorIRNodeReference Replace(RazorIRNode node)
{
if (node == null)
{
throw new ArgumentNullException(nameof(node));
}
if (Parent == null)
{
throw new InvalidOperationException(Resources.IRNodeReference_NotInitialized);
}
if (Parent.Children.IsReadOnly)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_CollectionIsReadOnly(Parent));
}
var index = Parent.Children.IndexOf(Node);
if (index == -1)
{
throw new InvalidOperationException(Resources.FormatIRNodeReference_NodeNotFound(
Node,
Parent));
}
Parent.Children[index] = node;
return new RazorIRNodeReference(Parent, node);
}
}
}

View File

@ -458,6 +458,48 @@ namespace Microsoft.AspNetCore.Razor.Language
internal static string FormatKeyMustNotBeNull()
=> GetString("KeyMustNotBeNull");
/// <summary>
/// The reference is invalid. The node '{0}' could not be found as a child of '{1}'.
/// </summary>
internal static string IRNodeReference_NodeNotFound
{
get => GetString("IRNodeReference_NodeNotFound");
}
/// <summary>
/// The reference is invalid. The node '{0}' could not be found as a child of '{1}'.
/// </summary>
internal static string FormatIRNodeReference_NodeNotFound(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("IRNodeReference_NodeNotFound"), p0, p1);
/// <summary>
/// The reference is invalid. References initialized with the default constructor cannot modify nodes.
/// </summary>
internal static string IRNodeReference_NotInitialized
{
get => GetString("IRNodeReference_NotInitialized");
}
/// <summary>
/// The reference is invalid. References initialized with the default constructor cannot modify nodes.
/// </summary>
internal static string FormatIRNodeReference_NotInitialized()
=> GetString("IRNodeReference_NotInitialized");
/// <summary>
/// The node '{0}' has a read-only child collection and cannot be modified.
/// </summary>
internal static string IRNodeReference_CollectionIsReadOnly
{
get => GetString("IRNodeReference_CollectionIsReadOnly");
}
/// <summary>
/// The node '{0}' has a read-only child collection and cannot be modified.
/// </summary>
internal static string FormatIRNodeReference_CollectionIsReadOnly(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("IRNodeReference_CollectionIsReadOnly"), p0);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -213,4 +213,13 @@
<data name="KeyMustNotBeNull" xml:space="preserve">
<value>The key must not be null.</value>
</data>
<data name="IRNodeReference_NodeNotFound" xml:space="preserve">
<value>The reference is invalid. The node '{0}' could not be found as a child of '{1}'.</value>
</data>
<data name="IRNodeReference_NotInitialized" xml:space="preserve">
<value>The reference is invalid. References initialized with the default constructor cannot modify nodes.</value>
</data>
<data name="IRNodeReference_CollectionIsReadOnly" xml:space="preserve">
<value>The node '{0}' has a read-only child collection and cannot be modified.</value>
</data>
</root>

View File

@ -60,5 +60,54 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
// Assert
Assert.Same(@namespace, result);
}
[Fact]
public void FindDirectiveReferences_FindsMatchingDirectives()
{
// Arrange
var directive = DirectiveDescriptor.CreateSingleLineDirective("test");
var directive2 = DirectiveDescriptor.CreateSingleLineDirective("test");
var document = new DocumentIRNode();
var @namespace = new NamespaceDeclarationIRNode();
var builder = RazorIRBuilder.Create(document);
builder.Push(@namespace);
var match1 = new DirectiveIRNode()
{
Descriptor = directive,
};
builder.Add(match1);
var nonMatch = new DirectiveIRNode()
{
Descriptor = directive2,
};
builder.Add(nonMatch);
var match2 = new DirectiveIRNode()
{
Descriptor = directive,
};
builder.Add(match2);
// Act
var results = document.FindDirectiveReferences(directive);
// Assert
Assert.Collection(
results,
r =>
{
Assert.Same(@namespace, r.Parent);
Assert.Same(match1, r.Node);
},
r =>
{
Assert.Same(@namespace, r.Parent);
Assert.Same(match2, r.Node);
});
}
}
}

View File

@ -0,0 +1,514 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
{
public class RazorIRNodeReferenceTest
{
[Fact]
public void InsertAfter_SingleNode_AddsNodeAfterNode()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node3);
var reference = new RazorIRNodeReference(parent, node1);
// Act
reference.InsertAfter(node2);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertAfter_SingleNode_AddsNodeAfterNode_AtEnd()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node2);
var reference = new RazorIRNodeReference(parent, node2);
// Act
reference.InsertAfter(node3);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertAfter_MultipleNodes_AddsNodesAfterNode()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
var node4 = new BasicIRNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node4);
var reference = new RazorIRNodeReference(parent, node1);
// Act
reference.InsertAfter(new[] { node2, node3 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void InsertAfter_MultipleNodes_AddsNodesAfterNode_AtEnd()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
var node4 = new BasicIRNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node2);
var reference = new RazorIRNodeReference(parent, node2);
// Act
reference.InsertAfter(new[] { node3, node4 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void InsertBefore_SingleNode_AddsNodeBeforeNode()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node3);
var reference = new RazorIRNodeReference(parent, node3);
// Act
reference.InsertBefore(node2);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertBefore_SingleNode_AddsNodeBeforeNode_AtBeginning()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
parent.Children.Add(node2);
parent.Children.Add(node3);
var reference = new RazorIRNodeReference(parent, node2);
// Act
reference.InsertBefore(node1);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertBefore_MultipleNodes_AddsNodesBeforeNode()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
var node4 = new BasicIRNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node4);
var reference = new RazorIRNodeReference(parent, node4);
// Act
reference.InsertBefore(new[] { node2, node3 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void InsertAfter_MultipleNodes_AddsNodesBeforeNode_AtBeginning()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
var node4 = new BasicIRNode("Node4");
parent.Children.Add(node3);
parent.Children.Add(node4);
var reference = new RazorIRNodeReference(parent, node3);
// Act
reference.InsertBefore(new[] { node1, node2 });
// Assert
Assert.Equal(new[] { node1, node2, node3, node4, }, parent.Children);
}
[Fact]
public void Remove_RemovesNode()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
parent.Children.Add(node1);
parent.Children.Add(node3);
parent.Children.Add(node2);
var reference = new RazorIRNodeReference(parent, node3);
// Act
reference.Remove();
// Assert
Assert.Equal(new[] { node1, node2,}, parent.Children);
}
[Fact]
public void Replace_ReplacesNode()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var node2 = new BasicIRNode("Node2");
var node3 = new BasicIRNode("Node3");
var node4 = new BasicIRNode("Node4");
parent.Children.Add(node1);
parent.Children.Add(node4);
parent.Children.Add(node3);
var reference = new RazorIRNodeReference(parent, node4);
// Act
reference.Replace(node2);
// Assert
Assert.Equal(new[] { node1, node2, node3, }, parent.Children);
}
[Fact]
public void InsertAfter_SingleNode_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new RazorIRNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new BasicIRNode("_")));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertAfter_MulipleNodes_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new RazorIRNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new[] { new BasicIRNode("_") }));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertBefore_SingleNode_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new RazorIRNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new BasicIRNode("_")));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertBefore_MulipleNodes_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new RazorIRNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new[] { new BasicIRNode("_") }));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void Remove_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new RazorIRNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Remove());
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void Replace_ThrowsForReferenceNotInitialized()
{
// Arrange
var reference = new RazorIRNodeReference();
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Replace(new BasicIRNode("_")));
Assert.Equal("The reference is invalid. References initialized with the default constructor cannot modify nodes.", exception.Message);
}
[Fact]
public void InsertAfter_SingleNode_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIRNode("Parent", ReadOnlyIRNodeCollection.Instance);
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new BasicIRNode("_")));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertAfter_MulipleNodes_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIRNode("Parent", ReadOnlyIRNodeCollection.Instance);
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new[] { new BasicIRNode("_") }));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertBefore_SingleNode_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIRNode("Parent", ReadOnlyIRNodeCollection.Instance);
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new BasicIRNode("_")));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertBefore_MulipleNodes_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIRNode("Parent", ReadOnlyIRNodeCollection.Instance);
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new[] { new BasicIRNode("_") }));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void Remove_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIRNode("Parent", ReadOnlyIRNodeCollection.Instance);
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Remove());
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void Replace_ThrowsForReadOnlyCollection()
{
// Arrange
var parent = new BasicIRNode("Parent", ReadOnlyIRNodeCollection.Instance);
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Replace(new BasicIRNode("_")));
Assert.Equal("The node 'Parent' has a read-only child collection and cannot be modified.", exception.Message);
}
[Fact]
public void InsertAfter_SingleNode_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new BasicIRNode("_")));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void InsertAfter_MulipleNodes_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertAfter(new[] { new BasicIRNode("_") }));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void InsertBefore_SingleNode_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new BasicIRNode("_")));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void InsertBefore_MulipleNodes_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.InsertBefore(new[] { new BasicIRNode("_") }));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void Remove_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Remove());
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
[Fact]
public void Replace_ThrowsForNodeNotFound()
{
// Arrange
var parent = new BasicIRNode("Parent");
var node1 = new BasicIRNode("Node1");
var reference = new RazorIRNodeReference(parent, node1);
// Act & Assert
var exception = Assert.Throws<InvalidOperationException>(() => reference.Replace(new BasicIRNode("_")));
Assert.Equal("The reference is invalid. The node 'Node1' could not be found as a child of 'Parent'.", exception.Message);
}
private class BasicIRNode : RazorIRNode
{
public BasicIRNode(string name)
: this(name, new DefaultIRNodeCollection())
{
Name = name;
}
public BasicIRNode(string name, RazorIRNodeCollection children)
{
Name = name;
Children = children;
}
public string Name { get; }
public override ItemCollection Annotations { get; } = new DefaultItemCollection();
public override RazorIRNodeCollection Children { get; }
public override SourceSpan? Source { get; set; }
public override void Accept(RazorIRNodeVisitor visitor)
{
throw new System.NotImplementedException();
}
public override string ToString() => Name;
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.Intermediate
@ -43,14 +44,68 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
Assert.Equal(nodes, walker.Visited.ToArray());
}
[Fact]
public void IRNodeWalker_Visit_SetsParentAndAncestors()
{
// Arrange
var walker = new DerivedIRNodeWalker();
var nodes = new RazorIRNode[]
{
new BasicIRNode("Root"),
new BasicIRNode("Root->A"),
new BasicIRNode("Root->B"),
new BasicIRNode("Root->B->1"),
new BasicIRNode("Root->B->2"),
new BasicIRNode("Root->C"),
};
var ancestors = new Dictionary<string, string[]>()
{
{ "Root", new string[]{ } },
{ "Root->A", new string[] { "Root" } },
{ "Root->B", new string[] { "Root" } },
{ "Root->B->1", new string[] { "Root->B", "Root" } },
{ "Root->B->2", new string[] { "Root->B", "Root" } },
{ "Root->C", new string[] { "Root" } },
};
walker.OnVisiting = (n) =>
{
Assert.Equal(ancestors[((BasicIRNode)n).Name], walker.Ancestors.Cast<BasicIRNode>().Select(b => b.Name));
Assert.Equal(ancestors[((BasicIRNode)n).Name].FirstOrDefault(), ((BasicIRNode)walker.Parent)?.Name);
};
var builder = new DefaultRazorIRBuilder();
builder.Push(nodes[0]);
builder.Add(nodes[1]);
builder.Push(nodes[2]);
builder.Add(nodes[3]);
builder.Add(nodes[4]);
builder.Pop();
builder.Add(nodes[5]);
var root = builder.Pop();
// Act & Assert
walker.Visit(root);
}
private class DerivedIRNodeWalker : RazorIRNodeWalker
{
public new IEnumerable<RazorIRNode> Ancestors => base.Ancestors;
public new RazorIRNode Parent => base.Parent;
public List<RazorIRNode> Visited { get; } = new List<RazorIRNode>();
public Action<RazorIRNode> OnVisiting { get; set; }
public override void VisitDefault(RazorIRNode node)
{
Visited.Add(node);
OnVisiting?.Invoke(node);
base.VisitDefault(node);
}
@ -60,7 +115,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
}
}
private class BasicIRNode : RazorIRNode
{
public BasicIRNode(string name)