Avoid globally qualifying user defined types (#69)
* Avoid globally qualifying explicit type parameters
This commit is contained in:
Родитель
49bbb54415
Коммит
080bb02308
|
@ -96,4 +96,14 @@ public static class BoundAttributeDescriptorBuilderExtensions
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void SetGloballyQualifiedTypeName(this BoundAttributeDescriptorBuilder builder, string globallyQualifiedTypeName)
|
||||
{
|
||||
builder.Metadata[TagHelperMetadata.Common.GloballyQualifiedTypeName] = globallyQualifiedTypeName;
|
||||
}
|
||||
|
||||
public static string GetGloballyQualifiedTypeName(this BoundAttributeDescriptor descriptor)
|
||||
{
|
||||
return descriptor?.Metadata[TagHelperMetadata.Common.GloballyQualifiedTypeName];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -714,7 +714,15 @@ internal class ComponentDesignTimeNodeWriter : ComponentNodeWriter
|
|||
{
|
||||
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||
context.CodeWriter.Write("<");
|
||||
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
|
||||
var explicitType = (bool?)node.Annotations[ComponentMetadata.Component.ExplicitTypeNameKey];
|
||||
if (explicitType == true)
|
||||
{
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
|
||||
}
|
||||
context.CodeWriter.Write(">");
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
|
|
@ -337,11 +337,11 @@ internal class ComponentGenericTypePass : ComponentIntermediateNodePassBase, IRa
|
|||
|
||||
foreach (var attribute in node.Attributes)
|
||||
{
|
||||
string globallyQualifiedTypeName = null;
|
||||
var globallyQualifiedTypeName = attribute.BoundAttribute.GetGloballyQualifiedTypeName();
|
||||
|
||||
if (attribute.TypeName != null)
|
||||
{
|
||||
globallyQualifiedTypeName = rewriter.Rewrite(attribute.TypeName);
|
||||
globallyQualifiedTypeName = rewriter.Rewrite(globallyQualifiedTypeName ?? attribute.TypeName);
|
||||
attribute.GloballyQualifiedTypeName = globallyQualifiedTypeName;
|
||||
}
|
||||
|
||||
|
@ -350,6 +350,18 @@ internal class ComponentGenericTypePass : ComponentIntermediateNodePassBase, IRa
|
|||
// If we know the type name, then replace any generic type parameter inside it with
|
||||
// the known types.
|
||||
attribute.TypeName = globallyQualifiedTypeName;
|
||||
// This is a special case in which we are dealing with a property TItem.
|
||||
// Given that TItem can have been defined explicitly by the user to a partially
|
||||
// qualified type, (like MyType), we check if the globally qualified type name
|
||||
// contains "global::" which will be the case in all cases as we've computed
|
||||
// this information from the Roslyn symbol except for when the symbol is a generic
|
||||
// type parameter. In which case, we mark it with an additional annotation to
|
||||
// acount for that during code generation and avoid trying to fully qualify
|
||||
// the type name.
|
||||
if (!globallyQualifiedTypeName.StartsWith("global::", StringComparison.Ordinal))
|
||||
{
|
||||
attribute.Annotations.Add(ComponentMetadata.Component.ExplicitTypeNameKey, true);
|
||||
}
|
||||
}
|
||||
else if (attribute.TypeName == null && (attribute.BoundAttribute?.IsDelegateProperty() ?? false))
|
||||
{
|
||||
|
|
|
@ -101,6 +101,8 @@ internal static class ComponentMetadata
|
|||
|
||||
public const string GenericTypedKey = "Components.GenericTyped";
|
||||
|
||||
public const string ExplicitTypeNameKey = "Components.ExplicitTypeName";
|
||||
|
||||
public const string TypeParameterKey = "Components.TypeParameter";
|
||||
|
||||
public const string TypeParameterIsCascadingKey = "Components.TypeParameterIsCascading";
|
||||
|
|
|
@ -294,7 +294,7 @@ internal abstract class ComponentNodeWriter : IntermediateNodeWriter, ITemplateT
|
|||
typeName = attribute.TypeName;
|
||||
if (attribute.BoundAttribute != null && !attribute.BoundAttribute.IsGenericTypedProperty())
|
||||
{
|
||||
typeName = "global::" + typeName;
|
||||
typeName = typeName.StartsWith("global::", StringComparison.Ordinal) ? typeName : $"global::{typeName}";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -653,7 +653,15 @@ internal class ComponentRuntimeNodeWriter : ComponentNodeWriter
|
|||
{
|
||||
context.CodeWriter.Write(ComponentsApi.RuntimeHelpers.TypeCheck);
|
||||
context.CodeWriter.Write("<");
|
||||
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
|
||||
var explicitType = (bool?)node.Annotations[ComponentMetadata.Component.ExplicitTypeNameKey];
|
||||
if (explicitType == true)
|
||||
{
|
||||
context.CodeWriter.Write(node.TypeName);
|
||||
}
|
||||
else
|
||||
{
|
||||
TypeNameHelper.WriteGloballyQualifiedName(context.CodeWriter, node.TypeName);
|
||||
}
|
||||
context.CodeWriter.Write(">");
|
||||
context.CodeWriter.Write("(");
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// Licensed to the .NET Foundation under one or more agreements.
|
||||
// The .NET Foundation licenses this file to you under the MIT license.
|
||||
|
||||
#nullable disable
|
||||
|
@ -13,6 +13,8 @@ public static class TagHelperMetadata
|
|||
|
||||
public static readonly string TypeName = "Common.TypeName";
|
||||
|
||||
public static readonly string GloballyQualifiedTypeName = "Common.GloballyQualifiedTypeName";
|
||||
|
||||
public static readonly string ClassifyAttributesOnly = "Common.ClassifyAttributesOnly";
|
||||
}
|
||||
|
||||
|
|
|
@ -3710,6 +3710,36 @@ namespace Test
|
|||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericComponent_NonPrimitiveType()
|
||||
{
|
||||
// Arrange
|
||||
AdditionalSyntaxTrees.Add(Parse(@"
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Test
|
||||
{
|
||||
public class MyComponent<TItem> : ComponentBase
|
||||
{
|
||||
[Parameter] public TItem Item { get; set; }
|
||||
}
|
||||
|
||||
public class CustomType
|
||||
{
|
||||
}
|
||||
}
|
||||
"));
|
||||
|
||||
// Act
|
||||
var generated = CompileToCSharp(@"
|
||||
<MyComponent TItem=""CustomType"" Item=""new CustomType()""/>");
|
||||
|
||||
// Assert
|
||||
AssertDocumentNodeMatchesBaseline(generated.CodeDocument);
|
||||
AssertCSharpDocumentMatchesBaseline(generated.CodeDocument);
|
||||
CompileToAssembly(generated);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChildComponent_Generic_TypeInference()
|
||||
{
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase
|
||||
{
|
||||
#pragma warning disable 219
|
||||
private void __RazorDirectiveTokenHelpers__() {
|
||||
}
|
||||
#pragma warning restore 219
|
||||
#pragma warning disable 0414
|
||||
private static System.Object __o = null;
|
||||
#pragma warning restore 0414
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
|
||||
{
|
||||
__o = typeof(
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
CustomType
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
);
|
||||
__o = global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<CustomType>(
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
new CustomType()
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
);
|
||||
__builder.AddAttribute(-1, "ChildContent", (global::Microsoft.AspNetCore.Components.RenderFragment)((__builder2) => {
|
||||
}
|
||||
));
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
__o = typeof(global::Test.MyComponent<>);
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
|
@ -0,0 +1,21 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [12] ) - System
|
||||
UsingDirective - (18:2,1 [32] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [17] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [28] ) - System.Threading.Tasks
|
||||
UsingDirective - (104:5,1 [37] ) - Microsoft.AspNetCore.Components
|
||||
ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase -
|
||||
DesignTimeDirective -
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning disable 0414
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - private static System.Object __o = null;
|
||||
CSharpCode -
|
||||
IntermediateToken - - CSharp - #pragma warning restore 0414
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
Component - (0:0,0 [57] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
|
||||
ComponentTypeArgument - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - TItem
|
||||
LazyIntermediateToken - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CustomType
|
||||
ComponentAttribute - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item - AttributeStructure.DoubleQuotes
|
||||
LazyIntermediateToken - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - new CustomType()
|
|
@ -0,0 +1,10 @@
|
|||
Source Location: (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|CustomType|
|
||||
Generated Location: (915:25,20 [10] )
|
||||
|CustomType|
|
||||
|
||||
Source Location: (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml)
|
||||
|new CustomType()|
|
||||
Generated Location: (1215:34,38 [16] )
|
||||
|new CustomType()|
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// <auto-generated/>
|
||||
#pragma warning disable 1591
|
||||
namespace Test
|
||||
{
|
||||
#line hidden
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
public partial class TestComponent : global::Microsoft.AspNetCore.Components.ComponentBase
|
||||
{
|
||||
#pragma warning disable 1998
|
||||
protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
|
||||
{
|
||||
__builder.OpenComponent<global::Test.MyComponent<CustomType>>(0);
|
||||
__builder.AddAttribute(1, "Item", global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck<CustomType>(
|
||||
#nullable restore
|
||||
#line 1 "x:\dir\subdir\Test\TestComponent.cshtml"
|
||||
new CustomType()
|
||||
|
||||
#line default
|
||||
#line hidden
|
||||
#nullable disable
|
||||
));
|
||||
__builder.CloseComponent();
|
||||
}
|
||||
#pragma warning restore 1998
|
||||
}
|
||||
}
|
||||
#pragma warning restore 1591
|
|
@ -0,0 +1,14 @@
|
|||
Document -
|
||||
NamespaceDeclaration - - Test
|
||||
UsingDirective - (3:1,1 [14] ) - System
|
||||
UsingDirective - (18:2,1 [34] ) - System.Collections.Generic
|
||||
UsingDirective - (53:3,1 [19] ) - System.Linq
|
||||
UsingDirective - (73:4,1 [30] ) - System.Threading.Tasks
|
||||
UsingDirective - (104:5,1 [39] ) - Microsoft.AspNetCore.Components
|
||||
ClassDeclaration - - public partial - TestComponent - global::Microsoft.AspNetCore.Components.ComponentBase -
|
||||
MethodDeclaration - - protected override - void - BuildRenderTree
|
||||
Component - (0:0,0 [57] x:\dir\subdir\Test\TestComponent.cshtml) - MyComponent
|
||||
ComponentTypeArgument - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - TItem
|
||||
LazyIntermediateToken - (20:0,20 [10] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - CustomType
|
||||
ComponentAttribute - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - Item - Item - AttributeStructure.DoubleQuotes
|
||||
LazyIntermediateToken - (38:0,38 [16] x:\dir\subdir\Test\TestComponent.cshtml) - CSharp - new CustomType()
|
|
@ -181,7 +181,7 @@ internal class ComponentTagHelperDescriptorProvider : RazorEngineFeatureBase, IT
|
|||
pb.TypeName = property.Type.ToDisplayString(FullNameTypeDisplayFormat);
|
||||
pb.SetPropertyName(property.Name);
|
||||
pb.IsEditorRequired = property.GetAttributes().Any(a => a.AttributeClass.ToDisplayString() == "Microsoft.AspNetCore.Components.EditorRequiredAttribute");
|
||||
|
||||
pb.SetGloballyQualifiedTypeName(property.Type.ToDisplayString(GloballyQualifiedFullNameTypeDisplayFormat));
|
||||
if (kind == PropertyKind.Enum)
|
||||
{
|
||||
pb.IsEnum = true;
|
||||
|
|
|
@ -59,7 +59,21 @@ internal class GlobalQualifiedTypeNameRewriter : TypeNameRewriter
|
|||
node = (QualifiedNameSyntax)base.VisitQualifiedName(node);
|
||||
|
||||
// Rewriting these is complicated, best to just tostring and parse again.
|
||||
return SyntaxFactory.ParseTypeName("global::" + node.ToString());
|
||||
return SyntaxFactory.ParseTypeName(IsGloballyQualified(node) ? node.ToString() : "global::" + node.ToString());
|
||||
|
||||
static bool IsGloballyQualified(QualifiedNameSyntax node)
|
||||
{
|
||||
var candidate = node;
|
||||
while (candidate != null)
|
||||
{
|
||||
if (candidate.Left is AliasQualifiedNameSyntax)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
candidate = candidate.Left as QualifiedNameSyntax;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override SyntaxNode VisitIdentifierName(IdentifierNameSyntax node)
|
||||
|
|
|
@ -131,6 +131,7 @@ namespace Test
|
|||
// which is trivial. Verifying it once in detail and then ignoring it.
|
||||
Assert.Collection(
|
||||
attribute.Metadata.OrderBy(kvp => kvp.Key),
|
||||
kvp => { Assert.Equal(TagHelperMetadata.Common.GloballyQualifiedTypeName, kvp.Key); Assert.Equal("global::System.String", kvp.Value); },
|
||||
kvp => { Assert.Equal(TagHelperMetadata.Common.PropertyName, kvp.Key); Assert.Equal("MyProperty", kvp.Value); });
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче