Avoid globally qualifying user defined types (#69)

* Avoid globally qualifying explicit type parameters
This commit is contained in:
Javier Calvarro Nelson 2022-02-04 01:34:57 -08:00 коммит произвёл GitHub
Родитель 49bbb54415
Коммит 080bb02308
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
16 изменённых файлов: 226 добавлений и 8 удалений

Просмотреть файл

@ -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); });
}