From eb3c47b6cabdc92e7efff9058b215d5281e1c9c6 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 May 2017 12:09:14 -0700 Subject: [PATCH] Add annotations Fixes #990. This should make a lot of IR manipulations very simple. --- .../DocumentClassifierPassBase.cs | 22 ++----- .../AddTagHelperHtmlAttributeIRNode.cs | 2 + .../CSharpAttributeValueIRNode.cs | 2 + .../Intermediate/CSharpExpressionIRNode.cs | 15 +++++ .../Intermediate/CSharpStatementIRNode.cs | 15 +++++ .../Intermediate/ChecksumIRNode.cs | 2 + .../Intermediate/ClassDeclarationIRNode.cs | 14 ++++ .../Intermediate/CommonAnnotations.cs | 14 ++++ .../Intermediate/CreateTagHelperIRNode.cs | 2 + .../DeclareTagHelperFieldsIRNode.cs | 2 + .../Intermediate/DirectiveIRNode.cs | 15 +++++ .../Intermediate/DirectiveTokenIRNode.cs | 2 + .../Intermediate/DocumentIRNode.cs | 15 +++++ .../Intermediate/DocumentIRNodeExtensions.cs | 59 +++++++++++++++++ .../Intermediate/ExecuteTagHelpersIRNode.cs | 2 + .../Intermediate/ExtensionIRNode.cs | 15 +++++ .../Intermediate/HtmlAttributeIRNode.cs | 2 + .../Intermediate/HtmlAttributeValueIRNode.cs | 2 + .../Intermediate/HtmlContentIRNode.cs | 2 + .../InitializeTagHelperStructureIRNode.cs | 5 +- .../NamespaceDeclarationIRNode.cs | 15 +++++ .../Intermediate/RazorIRNode.cs | 2 + .../Intermediate/RazorIRToken.cs | 2 + .../RazorMethodDeclarationIRNode.cs | 15 +++++ .../SetTagHelperPropertyIRNode.cs | 2 + .../Intermediate/TagHelperIRNode.cs | 15 +++++ .../Intermediate/UsingStatementIRNode.cs | 2 + .../ReadonlyItemCollection.cs | 18 ++++++ .../DocumentClassifierPassBaseTest.cs | 35 ++++++++++ .../Intermediate/DefaultRazorIRBuilderTest.cs | 2 + .../DocumentIRNodeExtensionsTest.cs | 64 +++++++++++++++++++ .../Intermediate/RazorIRAssert.cs | 31 +++++++++ .../RazorIRBuilderExtensionsTest.cs | 6 ++ .../Intermediate/RazorIRNodeWalkerTest.cs | 2 + 34 files changed, 402 insertions(+), 18 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNodeExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Razor.Language/ReadonlyItemCollection.cs create mode 100644 test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DocumentIRNodeExtensionsTest.cs diff --git a/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs b/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs index 221b95cc7f..f6daec24fa 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DocumentClassifierPassBase.cs @@ -48,24 +48,14 @@ namespace Microsoft.AspNetCore.Razor.Language var children = new List(irDocument.Children); irDocument.Children.Clear(); - var @namespace = new NamespaceDeclarationIRNode() - { - //Content = "GeneratedNamespace", - }; + var @namespace = new NamespaceDeclarationIRNode(); + @namespace.Annotations[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace; - var @class = new ClassDeclarationIRNode() - { - //AccessModifier = "public", - //Name = "GeneratedClass", - }; + var @class = new ClassDeclarationIRNode(); + @class.Annotations[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass; - var method = new RazorMethodDeclarationIRNode() - { - //AccessModifier = "public", - // Modifiers = new List() { "async" }, - //Name = "Execute", - //ReturnType = "Task", - }; + var method = new RazorMethodDeclarationIRNode(); + method.Annotations[CommonAnnotations.PrimaryMethod] = CommonAnnotations.PrimaryMethod; var documentBuilder = RazorIRBuilder.Create(irDocument); diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs index 6660a3f68e..6f206020ca 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/AddTagHelperHtmlAttributeIRNode.cs @@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class AddTagHelperHtmlAttributeIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpAttributeValueIRNode.cs index 6255c2547b..fedafd82fe 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpAttributeValueIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpAttributeValueIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class CSharpAttributeValueIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs index 0a9ca378a3..f9197721bb 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpExpressionIRNode.cs @@ -8,6 +8,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class CSharpExpressionIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpStatementIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpStatementIRNode.cs index c66c683a67..5c2a30f369 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpStatementIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CSharpStatementIRNode.cs @@ -8,6 +8,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class CSharpStatementIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs index 0e75680e4e..c130eb0884 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ChecksumIRNode.cs @@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class ChecksumIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children => EmptyArray; public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs index d0839cba69..e759f9ce9e 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ClassDeclarationIRNode.cs @@ -8,6 +8,20 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class ClassDeclarationIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs new file mode 100644 index 0000000000..b721520bfe --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CommonAnnotations.cs @@ -0,0 +1,14 @@ +// 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. + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate +{ + internal static class CommonAnnotations + { + public static readonly object PrimaryClass = "PrimaryClass"; + + public static readonly object PrimaryMethod = "PrimaryMethod"; + + public static readonly object PrimaryNamespace = "PrimaryNamespace"; + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs index 788dad54a8..647454a660 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/CreateTagHelperIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class CreateTagHelperIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = EmptyArray; public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs index 6ec498d543..2753b2df45 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DeclareTagHelperFieldsIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class DeclareTagHelperFieldsIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = EmptyArray; public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs index f3ff99e056..2c540c82a7 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveIRNode.cs @@ -9,6 +9,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class DirectiveIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs index 65b6c46dfe..a329569e8c 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DirectiveTokenIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class DirectiveTokenIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = EmptyArray; public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs index b3869e21bc..bb32972443 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNode.cs @@ -9,6 +9,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public sealed class DocumentIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public string DocumentKind { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNodeExtensions.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNodeExtensions.cs new file mode 100644 index 0000000000..2092dcdb84 --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/DocumentIRNodeExtensions.cs @@ -0,0 +1,59 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate +{ + public static class DocumentIRNodeExtensions + { + public static ClassDeclarationIRNode FindPrimaryClass(this DocumentIRNode node) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + return FindWithAnnotation(node, CommonAnnotations.PrimaryClass); + } + + public static RazorMethodDeclarationIRNode FindPrimaryMethod(this DocumentIRNode node) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + return FindWithAnnotation(node, CommonAnnotations.PrimaryMethod); + } + + public static NamespaceDeclarationIRNode FindPrimaryNamespace(this DocumentIRNode node) + { + if (node == null) + { + throw new ArgumentNullException(nameof(node)); + } + + return FindWithAnnotation(node, CommonAnnotations.PrimaryNamespace); + } + + private static T FindWithAnnotation(RazorIRNode node, object annotation) where T : RazorIRNode + { + if (node is T target && object.ReferenceEquals(target.Annotations[annotation], annotation)) + { + return target; + } + + for (var i = 0; i < node.Children.Count; i++) + { + var result = FindWithAnnotation(node.Children[i], annotation); + if (result != null) + { + return result; + } + } + + return null; + } + } +} diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExecuteTagHelpersIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExecuteTagHelpersIRNode.cs index e50cd3fe3c..537816efc6 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExecuteTagHelpersIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExecuteTagHelpersIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class ExecuteTagHelpersIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs index 96aa2dae28..a6537e9353 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/ExtensionIRNode.cs @@ -7,6 +7,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public abstract class ExtensionIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public abstract void WriteNode(RuntimeTarget target, CSharpRenderingContext context); protected static void AcceptExtensionNode(TNode node, RazorIRNodeVisitor visitor) diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs index 759e9ef762..161e03da31 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class HtmlAttributeIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs index bf1701653e..b50ba275c2 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlAttributeValueIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class HtmlAttributeValueIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = EmptyArray; public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs index 9483bc2439..86da14dd12 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/HtmlContentIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public sealed class HtmlContentIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InitializeTagHelperStructureIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InitializeTagHelperStructureIRNode.cs index e4cbe53fa1..e302497a24 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InitializeTagHelperStructureIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/InitializeTagHelperStructureIRNode.cs @@ -3,12 +3,13 @@ using System; using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Language.Legacy; namespace Microsoft.AspNetCore.Razor.Language.Intermediate { - public class InitializeTagHelperStructureIRNode : RazorIRNode + public sealed class InitializeTagHelperStructureIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs index 9775d95197..bfd63a86a5 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/NamespaceDeclarationIRNode.cs @@ -8,6 +8,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class NamespaceDeclarationIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs index 70f2c7f5d1..4475d4b048 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRNode.cs @@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { internal static readonly RazorIRNode[] EmptyArray = new RazorIRNode[0]; + public abstract ItemCollection Annotations { get; } + public abstract IList Children { get; } public abstract RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs index 1d01f4a01a..2c01d5e4b1 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorIRToken.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public sealed class RazorIRToken : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children => RazorIRNode.EmptyArray; public string Content { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorMethodDeclarationIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorMethodDeclarationIRNode.cs index 7f5e322049..471a64f1e8 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorMethodDeclarationIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/RazorMethodDeclarationIRNode.cs @@ -8,6 +8,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class RazorMethodDeclarationIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs index ccb4c35a88..2d5881c421 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/SetTagHelperPropertyIRNode.cs @@ -9,6 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class SetTagHelperPropertyIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs index a3730756ac..6d00bdc0f3 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/TagHelperIRNode.cs @@ -8,6 +8,21 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class TagHelperIRNode : RazorIRNode { + private ItemCollection _annotations; + + public override ItemCollection Annotations + { + get + { + if (_annotations == null) + { + _annotations = new DefaultItemCollection(); + } + + return _annotations; + } + } + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs index 60fc7d0021..dbc2dc5cca 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/Intermediate/UsingStatementIRNode.cs @@ -8,6 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate { public class UsingStatementIRNode : RazorIRNode { + public override ItemCollection Annotations => ReadonlyItemCollection.Empty; + public override IList Children { get; } = EmptyArray; public override RazorIRNode Parent { get; set; } diff --git a/src/Microsoft.AspNetCore.Razor.Language/ReadonlyItemCollection.cs b/src/Microsoft.AspNetCore.Razor.Language/ReadonlyItemCollection.cs new file mode 100644 index 0000000000..9d6e78e09e --- /dev/null +++ b/src/Microsoft.AspNetCore.Razor.Language/ReadonlyItemCollection.cs @@ -0,0 +1,18 @@ +// 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; + +namespace Microsoft.AspNetCore.Razor.Language +{ + internal class ReadonlyItemCollection : ItemCollection + { + public static readonly ItemCollection Empty = new ReadonlyItemCollection(); + + public override object this[object key] + { + get => null; + set => throw new NotSupportedException(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/DocumentClassifierPassBaseTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/DocumentClassifierPassBaseTest.cs index 44fc1ec7aa..f29241a36f 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/DocumentClassifierPassBaseTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/DocumentClassifierPassBaseTest.cs @@ -259,6 +259,41 @@ namespace Microsoft.AspNetCore.Razor.Language Assert.Equal("TestMethod", method.Name); } + [Fact] + public void Execute_AddsPrimaryAnnotations() + { + // Arrange + var irDocument = new DocumentIRNode() + { + Options = RazorParserOptions.CreateDefaultOptions(), + }; + + var builder = RazorIRBuilder.Create(irDocument); + builder.Add(new HtmlContentIRNode()); + builder.Add(new CSharpStatementIRNode()); + + var pass = new TestDocumentClassifierPass() + { + Engine = RazorEngine.CreateEmpty(b => { }), + Namespace = "TestNamespace", + Class = "TestClass", + Method = "TestMethod", + }; + + // Act + pass.Execute(TestRazorCodeDocument.CreateEmpty(), irDocument); + + // Assert + var @namespace = SingleChild(irDocument); + AnnotationEquals(@namespace, CommonAnnotations.PrimaryNamespace); + + var @class = SingleChild(@namespace); + AnnotationEquals(@class, CommonAnnotations.PrimaryClass); + + var method = SingleChild(@class); + AnnotationEquals(method, CommonAnnotations.PrimaryMethod); + } + private class TestDocumentClassifierPass : DocumentClassifierPassBase { public override int Order => RazorIRPass.DefaultFeatureOrder; diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs index dce1f6fd01..053a3db2b8 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DefaultRazorIRBuilderTest.cs @@ -214,6 +214,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate private class BasicIRNode : RazorIRNode { + public override ItemCollection Annotations { get; } = new DefaultItemCollection(); + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DocumentIRNodeExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DocumentIRNodeExtensionsTest.cs new file mode 100644 index 0000000000..0ab143306a --- /dev/null +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/DocumentIRNodeExtensionsTest.cs @@ -0,0 +1,64 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Razor.Language.Intermediate +{ + public class DocumentIRNodeExtensionsTest + { + [Fact] + public void FindPrimaryClass_FindsClassWithAnnotation() + { + // Arrange + var document = new DocumentIRNode(); + var @class = new ClassDeclarationIRNode(); + @class.Annotations[CommonAnnotations.PrimaryClass] = CommonAnnotations.PrimaryClass; + + var builder = RazorIRBuilder.Create(document); + builder.Add(@class); + + // Act + var result = document.FindPrimaryClass(); + + // Assert + Assert.Same(@class, result); + } + + [Fact] + public void FindPrimaryMethod_FindsMethodWithAnnotation() + { + // Arrange + var document = new DocumentIRNode(); + var method = new RazorMethodDeclarationIRNode(); + method.Annotations[CommonAnnotations.PrimaryMethod] = CommonAnnotations.PrimaryMethod; + + var builder = RazorIRBuilder.Create(document); + builder.Add(method); + + // Act + var result = document.FindPrimaryMethod(); + + // Assert + Assert.Same(method, result); + } + + [Fact] + public void FindPrimaryNamespace_FindsNamespaceWithAnnotation() + { + // Arrange + var document = new DocumentIRNode(); + var @namespace = new NamespaceDeclarationIRNode(); + @namespace.Annotations[CommonAnnotations.PrimaryNamespace] = CommonAnnotations.PrimaryNamespace; + + var builder = RazorIRBuilder.Create(document); + builder.Add(@namespace); + + // Act + var result = document.FindPrimaryNamespace(); + + // Assert + Assert.Same(@namespace, result); + } + } +} diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRAssert.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRAssert.cs index 5c4eb15c6e..be6ed58a20 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRAssert.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRAssert.cs @@ -61,6 +61,37 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate } } + public static void AnnotationEquals(RazorIRNode node, object value) + { + AnnotationEquals(node, value, value); + } + + public static void AnnotationEquals(RazorIRNode node, object key, object value) + { + try + { + Assert.NotNull(node); + Assert.Equal(value, node.Annotations[key]); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + + public static void HasAnnotation(RazorIRNode node, object key) + { + try + { + Assert.NotNull(node); + Assert.NotNull(node.Annotations[key]); + } + catch (XunitException e) + { + throw new IRAssertException(node, node.Children, e.Message, e); + } + } + public static void Html(string expected, RazorIRNode node) { try diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRBuilderExtensionsTest.cs index 65778ecbac..2e5b2ef4f5 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRBuilderExtensionsTest.cs @@ -74,6 +74,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate private class BasicIRNode : RazorIRNode { + public override ItemCollection Annotations { get; } = new DefaultItemCollection(); + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } @@ -88,6 +90,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate private class BasicIRNode2 : RazorIRNode { + public override ItemCollection Annotations { get; } = new DefaultItemCollection(); + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } @@ -102,6 +106,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate private class BasicIRNode3 : RazorIRNode { + public override ItemCollection Annotations { get; } = new DefaultItemCollection(); + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; } diff --git a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs index 04eab266cf..7ce6dcff00 100644 --- a/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs +++ b/test/Microsoft.AspNetCore.Razor.Language.Test/Intermediate/RazorIRNodeWalkerTest.cs @@ -70,6 +70,8 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate public string Name { get; } + public override ItemCollection Annotations { get; } = new DefaultItemCollection(); + public override IList Children { get; } = new List(); public override RazorIRNode Parent { get; set; }