Add support for type parameters to class nodes

This change will allow someone extending Razor to use generic type
parameters in generated classes.

There's no user-level extensibility provided here yet, as in there is no
language support for adding type parameters.
This commit is contained in:
Ryan Nowak 2018-04-05 13:23:20 -07:00
parent ae42d7599d
commit d1c0ab587c
10 changed files with 70 additions and 13 deletions

View File

@ -60,7 +60,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
WriteTargetElementString(context.CodeWriter, node.TagHelper); WriteTargetElementString(context.CodeWriter, node.TagHelper);
// Initialize declaration. // Initialize declaration.
using (context.CodeWriter.BuildClassDeclaration(PublicModifiers, node.ClassName, TagHelperTypeName, interfaces: null)) using (context.CodeWriter.BuildClassDeclaration(
PublicModifiers,
node.ClassName,
TagHelperTypeName,
interfaces: null,
typeParameters: null))
{ {
// Add view component helper. // Add view component helper.
context.CodeWriter.WriteVariableDeclaration( context.CodeWriter.WriteVariableDeclaration(

View File

@ -60,7 +60,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
WriteTargetElementString(context.CodeWriter, node.TagHelper); WriteTargetElementString(context.CodeWriter, node.TagHelper);
// Initialize declaration. // Initialize declaration.
using (context.CodeWriter.BuildClassDeclaration(PublicModifiers, node.ClassName, TagHelperTypeName, interfaces: null)) using (context.CodeWriter.BuildClassDeclaration(
PublicModifiers,
node.ClassName,
TagHelperTypeName,
interfaces: null,
typeParameters: null))
{ {
// Add view component helper. // Add view component helper.
context.CodeWriter.WriteVariableDeclaration( context.CodeWriter.WriteVariableDeclaration(

View File

@ -374,7 +374,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
IList<string> modifiers, IList<string> modifiers,
string name, string name,
string baseType, string baseType,
IEnumerable<string> interfaces) IList<string> interfaces,
IList<string> typeParameters)
{ {
for (var i = 0; i < modifiers.Count; i++) for (var i = 0; i < modifiers.Count; i++)
{ {
@ -385,8 +386,15 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
writer.Write("class "); writer.Write("class ");
writer.Write(name); writer.Write(name);
if (typeParameters != null && typeParameters.Count > 0)
{
writer.Write("<");
writer.Write(string.Join(", ", typeParameters));
writer.Write(">");
}
var hasBaseType = !string.IsNullOrEmpty(baseType); var hasBaseType = !string.IsNullOrEmpty(baseType);
var hasInterfaces = interfaces != null && interfaces.Count() > 0; var hasInterfaces = interfaces != null && interfaces.Count > 0;
if (hasBaseType || hasInterfaces) if (hasBaseType || hasInterfaces)
{ {

View File

@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
{ {
algorithmId = "{8829d00f-11b8-4213-878b-770e8597ac16}"; algorithmId = "{8829d00f-11b8-4213-878b-770e8597ac16}";
} }
else if (string.Equals(algorithm, HashAlgorithmName.SHA1.Name, StringComparison.Ordinal) || else if (string.Equals(algorithm, HashAlgorithmName.SHA1.Name, StringComparison.Ordinal) ||
// In 2.0, we didn't actually expose the name of the algorithm, so it's possible we could get null here. // In 2.0, we didn't actually expose the name of the algorithm, so it's possible we could get null here.
// If that's the case, we just assume SHA1 since that's the only thing we supported in 2.0. // If that's the case, we just assume SHA1 since that's the only thing we supported in 2.0.
algorithm == null) algorithm == null)
@ -88,7 +88,7 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
} }
else else
{ {
var supportedAlgorithms = string.Join(" ", new string[] var supportedAlgorithms = string.Join(" ", new string[]
{ {
HashAlgorithmName.SHA1.Name, HashAlgorithmName.SHA1.Name,
HashAlgorithmName.SHA256.Name HashAlgorithmName.SHA256.Name
@ -143,7 +143,12 @@ namespace Microsoft.AspNetCore.Razor.Language.CodeGeneration
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{ {
using (Context.CodeWriter.BuildClassDeclaration(node.Modifiers, node.ClassName, node.BaseType, node.Interfaces)) using (Context.CodeWriter.BuildClassDeclaration(
node.Modifiers,
node.ClassName,
node.BaseType,
node.Interfaces,
node.TypeParameters.Select(p => p.ParameterName).ToArray()))
{ {
VisitDefault(node); VisitDefault(node);
} }

View File

@ -16,7 +16,9 @@ namespace Microsoft.AspNetCore.Razor.Language.Intermediate
public string BaseType { get; set; } public string BaseType { get; set; }
public IList<string> Interfaces { get; set; } public IList<string> Interfaces { get; set; } = new List<string>();
public IList<TypeParameter> TypeParameters { get; set; } = new List<TypeParameter>();
public override void Accept(IntermediateNodeVisitor visitor) public override void Accept(IntermediateNodeVisitor visitor)
{ {

View File

@ -0,0 +1,10 @@
// 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
{
public sealed class TypeParameter
{
public string ParameterName { get; set; }
}
}

View File

@ -218,6 +218,11 @@ namespace TestNamespace
}, },
BaseType = "TestBase", BaseType = "TestBase",
Interfaces = new List<string> { "IFoo", "IBar", }, Interfaces = new List<string> { "IFoo", "IBar", },
TypeParameters = new List<TypeParameter>
{
new TypeParameter() { ParameterName = "TKey", },
new TypeParameter() { ParameterName = "TValue", },
},
ClassName = "TestClass", ClassName = "TestClass",
}); });
@ -236,7 +241,7 @@ namespace TestNamespace
@"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709"" @"#pragma checksum ""test.cshtml"" ""{ff1816ec-aa5e-4d10-87f7-6f4963833460}"" ""da39a3ee5e6b4b0d3255bfef95601890afd80709""
// <auto-generated/> // <auto-generated/>
#pragma warning disable 1591 #pragma warning disable 1591
internal class TestClass : TestBase, IFoo, IBar internal class TestClass<TKey, TValue> : TestBase, IFoo, IBar
{ {
} }
#pragma warning restore 1591 #pragma warning restore 1591

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.IO; using System.IO;
using Microsoft.AspNetCore.Razor.Language.Intermediate;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
@ -66,6 +67,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
engine.ConfigureClass((document, @class) => engine.ConfigureClass((document, @class) =>
{ {
@class.TypeParameters = new[] { new TypeParameter() { ParameterName = "TValue", }, };
@class.Interfaces = new[] { "global::System.IDisposable" }; @class.Interfaces = new[] { "global::System.IDisposable" };
@class.BaseType = "CustomBaseType"; @class.BaseType = "CustomBaseType";
}); });

View File

@ -5,7 +5,7 @@ namespace Razor
{ {
#line hidden #line hidden
[global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"38aa8e26c5d2a85c61d8e93fe69dd326fe82671b", @"/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.cshtml")] [global::Microsoft.AspNetCore.Razor.Hosting.RazorSourceChecksumAttribute(@"SHA1", @"38aa8e26c5d2a85c61d8e93fe69dd326fe82671b", @"/TestFiles/IntegrationTests/RazorTemplateEngineIntegrationTest/GenerateCodeWithConfigureClass.cshtml")]
protected internal class MyClass : CustomBaseType, global::System.IDisposable protected internal class MyClass<TValue> : CustomBaseType, global::System.IDisposable
{ {
#pragma warning disable 1998 #pragma warning disable 1998
public async override global::System.Threading.Tasks.Task ExecuteAsync() public async override global::System.Threading.Tasks.Task ExecuteAsync()

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
@ -12,7 +13,7 @@ using Microsoft.AspNetCore.Razor.Language.Intermediate;
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
{ {
// Serializes single IR nodes (shallow). // Serializes single IR nodes (shallow).
public class IntermediateNodeWriter : public class IntermediateNodeWriter :
IntermediateNodeVisitor, IntermediateNodeVisitor,
IExtensionIntermediateNodeVisitor<SectionIntermediateNode> IExtensionIntermediateNodeVisitor<SectionIntermediateNode>
{ {
@ -32,7 +33,21 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node) public override void VisitClassDeclaration(ClassDeclarationIntermediateNode node)
{ {
WriteContentNode(node, string.Join(" ", node.Modifiers), node.ClassName, node.BaseType, string.Join(", ", node.Interfaces ?? new List<string>())); var entries = new List<string>()
{
string.Join(" ", node.Modifiers),
node.ClassName,
node.BaseType,
string.Join(", ", node.Interfaces ?? Array.Empty<string>())
};
// Avoid adding the type parameters to the baseline if they aren't present.
if (node.TypeParameters != null && node.TypeParameters.Count > 0)
{
entries.Add(string.Join(", ", node.TypeParameters));
}
WriteContentNode(node, entries.ToArray());
} }
public override void VisitCSharpExpressionAttributeValue(CSharpExpressionAttributeValueIntermediateNode node) public override void VisitCSharpExpressionAttributeValue(CSharpExpressionAttributeValueIntermediateNode node)