Removing ITypeInfo and related types
This commit is contained in:
parent
c05551e167
commit
b4d631e2f6
|
|
@ -1,30 +0,0 @@
|
||||||
// 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.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Metadata common to types and properties.
|
|
||||||
/// </summary>
|
|
||||||
public interface IMemberInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the name.
|
|
||||||
/// </summary>
|
|
||||||
string Name { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a collection of custom <see cref="Attribute"/>s of type <typeparamref name="TAttribute"/> applied
|
|
||||||
/// to this instance of <see cref="IMemberInfo"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="TAttribute">The type of <see cref="Attribute"/> to search for.</typeparam>
|
|
||||||
/// <returns>A sequence of custom <see cref="Attribute"/>s of type
|
|
||||||
/// <typeparamref name="TAttribute"/>.</returns>
|
|
||||||
/// <remarks>Result not include inherited <see cref="Attribute"/>s.</remarks>
|
|
||||||
IEnumerable<TAttribute> GetCustomAttributes<TAttribute>()
|
|
||||||
where TAttribute : Attribute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
// 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.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains property metadata.
|
|
||||||
/// </summary>
|
|
||||||
public interface IPropertyInfo : IMemberInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this property has a public getter.
|
|
||||||
/// </summary>
|
|
||||||
bool HasPublicGetter { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether this property has a public setter.
|
|
||||||
/// </summary>
|
|
||||||
bool HasPublicSetter { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="ITypeInfo"/> of the property.
|
|
||||||
/// </summary>
|
|
||||||
ITypeInfo PropertyType { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
// 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.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Contains type metadata.
|
|
||||||
/// </summary>
|
|
||||||
public interface ITypeInfo : IMemberInfo, IEquatable<ITypeInfo>
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Fully qualified name of the type.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// On CoreCLR, some BCL types get type forwarded to the full desktop framework implementations at
|
|
||||||
/// runtime. For e.g. we compile against System.String in System.Runtime which is type forwarded to
|
|
||||||
/// mscorlib at runtime. Consequently for generic types where the <see cref="FullName"/> includes the assembly
|
|
||||||
/// qualified name of generic parameters, FullNames would not match.
|
|
||||||
/// Use <see cref="IEquatable{ITypeInfo}.Equals(ITypeInfo)"/> to compare <see cref="ITypeInfo"/>s instead.
|
|
||||||
/// </remarks>
|
|
||||||
string FullName { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets <see cref="IPropertyInfo"/>s for all properties of the current type excluding indexers.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Indexers in this context refer to the CLR notion of an indexer (<c>this [string name]</c>
|
|
||||||
/// and does not overlap with the semantics of
|
|
||||||
/// <see cref="Razor.Compilation.TagHelpers.TagHelperAttributeDescriptor.IsIndexer"/>.
|
|
||||||
/// </remarks>
|
|
||||||
IEnumerable<IPropertyInfo> Properties { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the type is an <see cref="Enum"/>.
|
|
||||||
/// </summary>
|
|
||||||
bool IsEnum { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the type is public.
|
|
||||||
/// </summary>
|
|
||||||
bool IsPublic { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the type is abstract or an interface.
|
|
||||||
/// </summary>
|
|
||||||
bool IsAbstract { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the type is generic.
|
|
||||||
/// </summary>
|
|
||||||
bool IsGenericType { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the type implements the <param name="interfaceTypeInfo"/> interface.
|
|
||||||
/// </summary>
|
|
||||||
bool ImplementsInterface(ITypeInfo interfaceTypeInfo);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of
|
|
||||||
/// <see cref="IDictionary{TKey, TValue}"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>
|
|
||||||
/// The <see cref="ITypeInfo"/> of <c>TKey</c> and <c>TValue</c>
|
|
||||||
/// parameters if the type implements <see cref="IDictionary{TKey, TValue}"/>, otherwise <c>null</c>.
|
|
||||||
/// </returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// For open generic types, <see cref="ITypeInfo" /> for generic type parameters is <c>null</c>.
|
|
||||||
/// </remarks>
|
|
||||||
ITypeInfo[] GetGenericDictionaryParameters();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
// 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.Reflection;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="IPropertyInfo"/> adapter for <see cref="PropertyInfo"/> instances.
|
|
||||||
/// </summary>
|
|
||||||
public class RuntimePropertyInfo : IPropertyInfo
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of <see cref="RuntimePropertyInfo"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyInfo">The <see cref="PropertyInfo"/> instance to adapt.</param>
|
|
||||||
public RuntimePropertyInfo(PropertyInfo propertyInfo)
|
|
||||||
{
|
|
||||||
if (propertyInfo == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(propertyInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
Property = propertyInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="PropertyInfo"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public PropertyInfo Property { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool HasPublicGetter => Property.GetMethod != null && Property.GetMethod.IsPublic;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool HasPublicSetter => Property.SetMethod != null && Property.SetMethod.IsPublic;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => Property.Name;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ITypeInfo PropertyType => new RuntimeTypeInfo(Property.PropertyType.GetTypeInfo());
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
|
|
||||||
=> Property.GetCustomAttributes<TAttribute>(inherit: false);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString() =>
|
|
||||||
Property.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
// 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.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
|
||||||
using Microsoft.Extensions.Internal;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// <see cref="ITypeInfo"/> adapter for <see cref="System.Reflection.TypeInfo"/> instances.
|
|
||||||
/// </summary>
|
|
||||||
public class RuntimeTypeInfo : ITypeInfo
|
|
||||||
{
|
|
||||||
private static readonly Regex _fullNameSanitizer = new Regex(
|
|
||||||
@", [A-Za-z\.]+, Version=\d+\.\d+\.\d+\.\d+, Culture=neutral, PublicKeyToken=\w+",
|
|
||||||
RegexOptions.ExplicitCapture,
|
|
||||||
Constants.RegexMatchTimeout);
|
|
||||||
|
|
||||||
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
|
|
||||||
private IEnumerable<IPropertyInfo> _properties;
|
|
||||||
private string _sanitizedFullName;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of <see cref="RuntimeTypeInfo"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="propertyInfo">The <see cref="System.Reflection.TypeInfo"/> instance to adapt.</param>
|
|
||||||
public RuntimeTypeInfo(TypeInfo typeInfo)
|
|
||||||
{
|
|
||||||
if (typeInfo == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(typeInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
TypeInfo = typeInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The <see cref="System.Reflection.TypeInfo"/> instance.
|
|
||||||
/// </summary>
|
|
||||||
public TypeInfo TypeInfo { get; }
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string Name => TypeInfo.Name;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public string FullName => TypeInfo.FullName;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsEnum => TypeInfo.IsEnum;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsAbstract => TypeInfo.IsAbstract;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsGenericType => TypeInfo.IsGenericType;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool IsPublic => TypeInfo.IsPublic;
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerable<IPropertyInfo> Properties
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_properties == null)
|
|
||||||
{
|
|
||||||
_properties = TypeInfo
|
|
||||||
.AsType()
|
|
||||||
.GetRuntimeProperties()
|
|
||||||
.Where(property => property.GetIndexParameters().Length == 0)
|
|
||||||
.Select(property => new RuntimePropertyInfo(property));
|
|
||||||
}
|
|
||||||
|
|
||||||
return _properties;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool ImplementsInterface(ITypeInfo interfaceTypeInfo)
|
|
||||||
{
|
|
||||||
if (interfaceTypeInfo == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(interfaceTypeInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
var runtimeTypeInfo = interfaceTypeInfo as RuntimeTypeInfo;
|
|
||||||
if (runtimeTypeInfo == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException(
|
|
||||||
Resources.FormatArgumentMustBeAnInstanceOf(typeof(RuntimeTypeInfo).FullName),
|
|
||||||
nameof(interfaceTypeInfo));
|
|
||||||
}
|
|
||||||
|
|
||||||
return runtimeTypeInfo.TypeInfo.IsInterface && runtimeTypeInfo.TypeInfo.IsAssignableFrom(TypeInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
private string SanitizedFullName
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (_sanitizedFullName == null)
|
|
||||||
{
|
|
||||||
_sanitizedFullName = SanitizeFullName(FullName);
|
|
||||||
}
|
|
||||||
|
|
||||||
return _sanitizedFullName;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute =>
|
|
||||||
TypeInfo.GetCustomAttributes<TAttribute>(inherit: false);
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public ITypeInfo[] GetGenericDictionaryParameters()
|
|
||||||
{
|
|
||||||
return ClosedGenericMatcher.ExtractGenericInterface(
|
|
||||||
TypeInfo.AsType(),
|
|
||||||
typeof(IDictionary<,>))
|
|
||||||
?.GenericTypeArguments
|
|
||||||
.Select(type => type.IsGenericParameter ? null : new RuntimeTypeInfo(type.GetTypeInfo()))
|
|
||||||
.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override string ToString() => TypeInfo.ToString();
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override bool Equals(object obj)
|
|
||||||
{
|
|
||||||
return Equals(obj as ITypeInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public bool Equals(ITypeInfo other)
|
|
||||||
{
|
|
||||||
if (other == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var otherRuntimeType = other as RuntimeTypeInfo;
|
|
||||||
if (otherRuntimeType != null)
|
|
||||||
{
|
|
||||||
return otherRuntimeType.TypeInfo == TypeInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string.Equals(
|
|
||||||
SanitizedFullName,
|
|
||||||
SanitizeFullName(other.FullName),
|
|
||||||
StringComparison.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
|
||||||
public override int GetHashCode() => SanitizedFullName.GetHashCode();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes assembly qualification from generic type parameters for the specified <paramref name="fullName"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="fullName">Full name.</param>
|
|
||||||
/// <returns>Full name without fully qualified generic parameters.</returns>
|
|
||||||
/// <example>
|
|
||||||
/// <c>typeof(<see cref="List{string}"/>).FullName</c> is
|
|
||||||
/// List`1[[System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]
|
|
||||||
/// <c>Sanitize(typeof(<see cref="List{string}"/>.FullName</c> returns
|
|
||||||
/// List`1[[System.String]
|
|
||||||
/// </example>
|
|
||||||
public static string SanitizeFullName(string fullName)
|
|
||||||
{
|
|
||||||
// In CoreCLR, some types (such as System.String) are type forwarded from System.Runtime
|
|
||||||
// to mscorlib at runtime. Type names of generic type parameters includes the assembly qualified name;
|
|
||||||
// consequently the type name generated at precompilation differs from the one at runtime. We'll
|
|
||||||
// avoid dealing with these inconsistencies by removing assembly information from TypeInfo.FullName.
|
|
||||||
return _fullNameSanitizer.Replace(fullName, string.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -9,11 +9,12 @@ using System.Reflection;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
using Microsoft.AspNet.Razor.TagHelpers;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Factory for <see cref="TagHelperDescriptor"/>s from <see cref="ITypeInfo"/>s.
|
/// Factory for <see cref="TagHelperDescriptor"/>s from <see cref="Type"/>s.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TagHelperDescriptorFactory
|
public class TagHelperDescriptorFactory
|
||||||
{
|
{
|
||||||
|
|
@ -31,8 +32,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
RegexOptions.None,
|
RegexOptions.None,
|
||||||
Constants.RegexMatchTimeout);
|
Constants.RegexMatchTimeout);
|
||||||
|
|
||||||
private static readonly ITypeInfo StringTypeInfo = new RuntimeTypeInfo(typeof(string).GetTypeInfo());
|
|
||||||
|
|
||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
private readonly TagHelperDesignTimeDescriptorFactory _designTimeDescriptorFactory;
|
private readonly TagHelperDesignTimeDescriptorFactory _designTimeDescriptorFactory;
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -63,24 +62,24 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a <see cref="TagHelperDescriptor"/> from the given <paramref name="typeInfo"/>.
|
/// Creates a <see cref="TagHelperDescriptor"/> from the given <paramref name="type"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
|
/// <param name="assemblyName">The assembly name that contains <paramref name="type"/>.</param>
|
||||||
/// <param name="typeInfo">The <see cref="ITypeInfo"/> to create a <see cref="TagHelperDescriptor"/> from.
|
/// <param name="type">The <see cref="Type"/> to create a <see cref="TagHelperDescriptor"/> from.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="errorSink">The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
|
/// <param name="errorSink">The <see cref="ErrorSink"/> used to collect <see cref="RazorError"/>s encountered
|
||||||
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="typeInfo"/>.</param>
|
/// when creating <see cref="TagHelperDescriptor"/>s for the given <paramref name="type"/>.</param>
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="typeInfo"/>.
|
/// A collection of <see cref="TagHelperDescriptor"/>s that describe the given <paramref name="type"/>.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
public virtual IEnumerable<TagHelperDescriptor> CreateDescriptors(
|
public virtual IEnumerable<TagHelperDescriptor> CreateDescriptors(
|
||||||
string assemblyName,
|
string assemblyName,
|
||||||
ITypeInfo typeInfo,
|
Type type,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
if (typeInfo == null)
|
if (type == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(typeInfo));
|
throw new ArgumentNullException(nameof(type));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorSink == null)
|
if (errorSink == null)
|
||||||
|
|
@ -88,18 +87,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
throw new ArgumentNullException(nameof(errorSink));
|
throw new ArgumentNullException(nameof(errorSink));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldSkipDescriptorCreation(typeInfo))
|
if (ShouldSkipDescriptorCreation(type.GetTypeInfo()))
|
||||||
{
|
{
|
||||||
return Enumerable.Empty<TagHelperDescriptor>();
|
return Enumerable.Empty<TagHelperDescriptor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributeDescriptors = GetAttributeDescriptors(typeInfo, errorSink);
|
var attributeDescriptors = GetAttributeDescriptors(type, errorSink);
|
||||||
var targetElementAttributes = GetValidHtmlTargetElementAttributes(typeInfo, errorSink);
|
var targetElementAttributes = GetValidHtmlTargetElementAttributes(type, errorSink);
|
||||||
var allowedChildren = GetAllowedChildren(typeInfo, errorSink);
|
var allowedChildren = GetAllowedChildren(type, errorSink);
|
||||||
|
|
||||||
var tagHelperDescriptors =
|
var tagHelperDescriptors =
|
||||||
BuildTagHelperDescriptors(
|
BuildTagHelperDescriptors(
|
||||||
typeInfo,
|
type,
|
||||||
assemblyName,
|
assemblyName,
|
||||||
attributeDescriptors,
|
attributeDescriptors,
|
||||||
targetElementAttributes,
|
targetElementAttributes,
|
||||||
|
|
@ -109,17 +108,18 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<HtmlTargetElementAttribute> GetValidHtmlTargetElementAttributes(
|
private static IEnumerable<HtmlTargetElementAttribute> GetValidHtmlTargetElementAttributes(
|
||||||
ITypeInfo typeInfo,
|
Type type,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var targetElementAttributes = typeInfo.GetCustomAttributes<HtmlTargetElementAttribute>();
|
var targetElementAttributes = type
|
||||||
|
.GetTypeInfo()
|
||||||
|
.GetCustomAttributes<HtmlTargetElementAttribute>(inherit: false);
|
||||||
return targetElementAttributes.Where(
|
return targetElementAttributes.Where(
|
||||||
attribute => ValidHtmlTargetElementAttributeNames(attribute, errorSink));
|
attribute => ValidHtmlTargetElementAttributeNames(attribute, errorSink));
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TagHelperDescriptor> BuildTagHelperDescriptors(
|
private IEnumerable<TagHelperDescriptor> BuildTagHelperDescriptors(
|
||||||
ITypeInfo typeInfo,
|
Type type,
|
||||||
string assemblyName,
|
string assemblyName,
|
||||||
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
IEnumerable<TagHelperAttributeDescriptor> attributeDescriptors,
|
||||||
IEnumerable<HtmlTargetElementAttribute> targetElementAttributes,
|
IEnumerable<HtmlTargetElementAttribute> targetElementAttributes,
|
||||||
|
|
@ -130,21 +130,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
if (_designTime)
|
if (_designTime)
|
||||||
{
|
{
|
||||||
var runtimeTypeInfo = typeInfo as RuntimeTypeInfo;
|
typeDesignTimeDescriptor = _designTimeDescriptorFactory.CreateDescriptor(type);
|
||||||
if (runtimeTypeInfo != null)
|
|
||||||
{
|
|
||||||
typeDesignTimeDescriptor =
|
|
||||||
_designTimeDescriptorFactory.CreateDescriptor(runtimeTypeInfo.TypeInfo.AsType());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
var typeName = typeInfo.FullName;
|
var typeName = type.FullName;
|
||||||
|
|
||||||
// If there isn't an attribute specifying the tag name derive it from the name
|
// If there isn't an attribute specifying the tag name derive it from the name
|
||||||
if (!targetElementAttributes.Any())
|
if (!targetElementAttributes.Any())
|
||||||
{
|
{
|
||||||
var name = typeInfo.Name;
|
var name = type.Name;
|
||||||
|
|
||||||
if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
|
if (name.EndsWith(TagHelperNameEnding, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
|
@ -177,18 +172,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
typeDesignTimeDescriptor));
|
typeDesignTimeDescriptor));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<string> GetAllowedChildren(ITypeInfo typeInfo, ErrorSink errorSink)
|
private static IEnumerable<string> GetAllowedChildren(Type type, ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var restrictChildrenAttribute = typeInfo
|
var restrictChildrenAttribute = type.GetTypeInfo().GetCustomAttribute<RestrictChildrenAttribute>(inherit: false);
|
||||||
.GetCustomAttributes<RestrictChildrenAttribute>()
|
|
||||||
.FirstOrDefault();
|
|
||||||
if (restrictChildrenAttribute == null)
|
if (restrictChildrenAttribute == null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var allowedChildren = restrictChildrenAttribute.ChildTags;
|
var allowedChildren = restrictChildrenAttribute.ChildTags;
|
||||||
var validAllowedChildren = GetValidAllowedChildren(allowedChildren, typeInfo.FullName, errorSink);
|
var validAllowedChildren = GetValidAllowedChildren(allowedChildren, type.FullName, errorSink);
|
||||||
|
|
||||||
if (validAllowedChildren.Any())
|
if (validAllowedChildren.Any())
|
||||||
{
|
{
|
||||||
|
|
@ -405,14 +398,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
return validName;
|
return validName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(ITypeInfo type, ErrorSink errorSink)
|
private IEnumerable<TagHelperAttributeDescriptor> GetAttributeDescriptors(Type type, ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
var attributeDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
// Keep indexer descriptors separate to avoid sorting the combined list later.
|
// Keep indexer descriptors separate to avoid sorting the combined list later.
|
||||||
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
|
var indexerDescriptors = new List<TagHelperAttributeDescriptor>();
|
||||||
|
|
||||||
var accessibleProperties = type.Properties.Where(IsAccessibleProperty);
|
var accessibleProperties = type.GetRuntimeProperties().Where(IsAccessibleProperty);
|
||||||
foreach (var property in accessibleProperties)
|
foreach (var property in accessibleProperties)
|
||||||
{
|
{
|
||||||
if (ShouldSkipDescriptorCreation(property))
|
if (ShouldSkipDescriptorCreation(property))
|
||||||
|
|
@ -421,14 +414,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
var attributeNameAttribute = property
|
var attributeNameAttribute = property
|
||||||
.GetCustomAttributes<HtmlAttributeNameAttribute>()
|
.GetCustomAttributes<HtmlAttributeNameAttribute>(inherit: false)
|
||||||
.FirstOrDefault();
|
.FirstOrDefault();
|
||||||
var hasExplicitName =
|
var hasExplicitName =
|
||||||
attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
|
attributeNameAttribute != null && !string.IsNullOrEmpty(attributeNameAttribute.Name);
|
||||||
var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);
|
var attributeName = hasExplicitName ? attributeNameAttribute.Name : ToHtmlCase(property.Name);
|
||||||
|
|
||||||
TagHelperAttributeDescriptor mainDescriptor = null;
|
TagHelperAttributeDescriptor mainDescriptor = null;
|
||||||
if (property.HasPublicSetter)
|
if (property.SetMethod != null && property.SetMethod.IsPublic)
|
||||||
{
|
{
|
||||||
mainDescriptor = ToAttributeDescriptor(property, attributeName);
|
mainDescriptor = ToAttributeDescriptor(property, attributeName);
|
||||||
if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
|
if (!ValidateTagHelperAttributeDescriptor(mainDescriptor, type, errorSink))
|
||||||
|
|
@ -491,7 +484,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Internal for testing.
|
// Internal for testing.
|
||||||
internal static bool ValidateTagHelperAttributeDescriptor(
|
internal static bool ValidateTagHelperAttributeDescriptor(
|
||||||
TagHelperAttributeDescriptor attributeDescriptor,
|
TagHelperAttributeDescriptor attributeDescriptor,
|
||||||
ITypeInfo parentType,
|
Type parentType,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
{
|
{
|
||||||
string nameOrPrefix;
|
string nameOrPrefix;
|
||||||
|
|
@ -523,13 +516,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
nameOrPrefix);
|
nameOrPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ShouldSkipDescriptorCreation(IMemberInfo memberInfo)
|
private bool ShouldSkipDescriptorCreation(MemberInfo memberInfo)
|
||||||
{
|
{
|
||||||
if (_designTime)
|
if (_designTime)
|
||||||
{
|
{
|
||||||
var editorBrowsableAttribute = memberInfo
|
var editorBrowsableAttribute = memberInfo.GetCustomAttribute<EditorBrowsableAttribute>(inherit: false);
|
||||||
.GetCustomAttributes<EditorBrowsableAttribute>()
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
return editorBrowsableAttribute != null &&
|
return editorBrowsableAttribute != null &&
|
||||||
editorBrowsableAttribute.State == EditorBrowsableState.Never;
|
editorBrowsableAttribute.State == EditorBrowsableState.Never;
|
||||||
|
|
@ -540,7 +531,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
|
|
||||||
private static bool ValidateTagHelperAttributeNameOrPrefix(
|
private static bool ValidateTagHelperAttributeNameOrPrefix(
|
||||||
string attributeNameOrPrefix,
|
string attributeNameOrPrefix,
|
||||||
ITypeInfo parentType,
|
Type parentType,
|
||||||
string propertyName,
|
string propertyName,
|
||||||
ErrorSink errorSink,
|
ErrorSink errorSink,
|
||||||
string nameOrPrefix)
|
string nameOrPrefix)
|
||||||
|
|
@ -608,28 +599,33 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
return isValid;
|
return isValid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagHelperAttributeDescriptor ToAttributeDescriptor(IPropertyInfo property, string attributeName)
|
private TagHelperAttributeDescriptor ToAttributeDescriptor(PropertyInfo property, string attributeName)
|
||||||
{
|
{
|
||||||
return ToAttributeDescriptor(
|
return ToAttributeDescriptor(
|
||||||
property,
|
property,
|
||||||
attributeName,
|
attributeName,
|
||||||
property.PropertyType.FullName,
|
property.PropertyType.FullName,
|
||||||
isIndexer: false,
|
isIndexer: false,
|
||||||
isStringProperty: StringTypeInfo.Equals(property.PropertyType));
|
isStringProperty: typeof(string) == property.PropertyType);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
|
private TagHelperAttributeDescriptor ToIndexerAttributeDescriptor(
|
||||||
IPropertyInfo property,
|
PropertyInfo property,
|
||||||
HtmlAttributeNameAttribute attributeNameAttribute,
|
HtmlAttributeNameAttribute attributeNameAttribute,
|
||||||
ITypeInfo parentType,
|
Type parentType,
|
||||||
ErrorSink errorSink,
|
ErrorSink errorSink,
|
||||||
string defaultPrefix,
|
string defaultPrefix,
|
||||||
out bool isInvalid)
|
out bool isInvalid)
|
||||||
{
|
{
|
||||||
isInvalid = false;
|
isInvalid = false;
|
||||||
var hasPublicSetter = property.HasPublicSetter;
|
var hasPublicSetter = property.SetMethod != null && property.SetMethod.IsPublic;
|
||||||
var dictionaryTypeArguments = property.PropertyType.GetGenericDictionaryParameters();
|
var dictionaryTypeArguments = ClosedGenericMatcher.ExtractGenericInterface(
|
||||||
if (!StringTypeInfo.Equals(dictionaryTypeArguments?[0]))
|
property.PropertyType,
|
||||||
|
typeof(IDictionary<,>))
|
||||||
|
?.GenericTypeArguments
|
||||||
|
.Select(type => type.IsGenericParameter ? null : type)
|
||||||
|
.ToArray();
|
||||||
|
if (dictionaryTypeArguments?[0] != typeof(string))
|
||||||
{
|
{
|
||||||
if (attributeNameAttribute?.DictionaryAttributePrefix != null)
|
if (attributeNameAttribute?.DictionaryAttributePrefix != null)
|
||||||
{
|
{
|
||||||
|
|
@ -698,11 +694,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
attributeName: prefix,
|
attributeName: prefix,
|
||||||
typeName: dictionaryTypeArguments[1].FullName,
|
typeName: dictionaryTypeArguments[1].FullName,
|
||||||
isIndexer: true,
|
isIndexer: true,
|
||||||
isStringProperty: StringTypeInfo.Equals(dictionaryTypeArguments[1]));
|
isStringProperty: typeof(string) == dictionaryTypeArguments[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private TagHelperAttributeDescriptor ToAttributeDescriptor(
|
private TagHelperAttributeDescriptor ToAttributeDescriptor(
|
||||||
IPropertyInfo property,
|
PropertyInfo property,
|
||||||
string attributeName,
|
string attributeName,
|
||||||
string typeName,
|
string typeName,
|
||||||
bool isIndexer,
|
bool isIndexer,
|
||||||
|
|
@ -713,12 +709,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
#if !DOTNET5_4
|
#if !DOTNET5_4
|
||||||
if (_designTime)
|
if (_designTime)
|
||||||
{
|
{
|
||||||
var runtimeProperty = property as RuntimePropertyInfo;
|
propertyDesignTimeDescriptor = _designTimeDescriptorFactory.CreateAttributeDescriptor(property);
|
||||||
if (runtimeProperty != null)
|
|
||||||
{
|
|
||||||
propertyDesignTimeDescriptor =
|
|
||||||
_designTimeDescriptorFactory.CreateAttributeDescriptor(runtimeProperty.Property);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
@ -726,7 +717,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
Name = attributeName,
|
Name = attributeName,
|
||||||
PropertyName = property.Name,
|
PropertyName = property.Name,
|
||||||
IsEnum = property.PropertyType.IsEnum,
|
IsEnum = property.PropertyType.GetTypeInfo().IsEnum,
|
||||||
TypeName = typeName,
|
TypeName = typeName,
|
||||||
IsStringProperty = isStringProperty,
|
IsStringProperty = isStringProperty,
|
||||||
IsIndexer = isIndexer,
|
IsIndexer = isIndexer,
|
||||||
|
|
@ -734,11 +725,13 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsAccessibleProperty(IPropertyInfo property)
|
private static bool IsAccessibleProperty(PropertyInfo property)
|
||||||
{
|
{
|
||||||
// Accessible properties are those with public getters and without [HtmlAttributeNotBound].
|
// Accessible properties are those with public getters and without [HtmlAttributeNotBound].
|
||||||
return property.HasPublicGetter &&
|
return property.GetIndexParameters().Length == 0 &&
|
||||||
property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>().FirstOrDefault() == null;
|
property.GetMethod != null &&
|
||||||
|
property.GetMethod.IsPublic &&
|
||||||
|
property.GetCustomAttribute<HtmlAttributeNotBoundAttribute>(inherit: false) == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TagHelperTypeResolver
|
public class TagHelperTypeResolver
|
||||||
{
|
{
|
||||||
private static readonly ITypeInfo ITagHelperTypeInfo = new RuntimeTypeInfo(typeof(ITagHelper).GetTypeInfo());
|
private static readonly TypeInfo ITagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Locates valid <see cref="ITagHelper"/> types from the <see cref="Assembly"/> named <paramref name="name"/>.
|
/// Locates valid <see cref="ITagHelper"/> types from the <see cref="Assembly"/> named <paramref name="name"/>.
|
||||||
|
|
@ -25,8 +25,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <param name="errorSink">The <see cref="ErrorSink"/> used to record errors found when resolving
|
/// <param name="errorSink">The <see cref="ErrorSink"/> used to record errors found when resolving
|
||||||
/// <see cref="ITagHelper"/> types.</param>
|
/// <see cref="ITagHelper"/> types.</param>
|
||||||
/// <returns>An <see cref="IEnumerable{ITypeInfo}"/> of valid <see cref="ITagHelper"/> types.</returns>
|
/// <returns>An <see cref="IEnumerable{Type}"/> of valid <see cref="ITagHelper"/> types.</returns>
|
||||||
public IEnumerable<ITypeInfo> Resolve(
|
public IEnumerable<Type> Resolve(
|
||||||
string name,
|
string name,
|
||||||
SourceLocation documentLocation,
|
SourceLocation documentLocation,
|
||||||
ErrorSink errorSink)
|
ErrorSink errorSink)
|
||||||
|
|
@ -44,15 +44,15 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
|
Resources.TagHelperTypeResolver_TagHelperAssemblyNameCannotBeEmptyOrNull,
|
||||||
errorLength);
|
errorLength);
|
||||||
|
|
||||||
return Enumerable.Empty<ITypeInfo>();
|
return Type.EmptyTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
var assemblyName = new AssemblyName(name);
|
var assemblyName = new AssemblyName(name);
|
||||||
|
|
||||||
IEnumerable<ITypeInfo> libraryTypes;
|
IEnumerable<TypeInfo> libraryTypes;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
libraryTypes = GetTopLevelExportedTypes(assemblyName);
|
libraryTypes = GetExportedTypes(assemblyName);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -63,31 +63,10 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
ex.Message),
|
ex.Message),
|
||||||
name.Length);
|
name.Length);
|
||||||
|
|
||||||
return Enumerable.Empty<ITypeInfo>();
|
return Type.EmptyTypes;
|
||||||
}
|
}
|
||||||
|
|
||||||
return libraryTypes.Where(IsTagHelper);
|
return libraryTypes.Where(IsTagHelper).Select(t => t.AsType());
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns all non-nested exported types from the given <paramref name="assemblyName"/>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="assemblyName">The <see cref="AssemblyName"/> to get <see cref="ITypeInfo"/>s from.</param>
|
|
||||||
/// <returns>
|
|
||||||
/// An <see cref="IEnumerable{ITypeInfo}"/> of types exported from the given <paramref name="assemblyName"/>.
|
|
||||||
/// </returns>
|
|
||||||
protected virtual IEnumerable<ITypeInfo> GetTopLevelExportedTypes(AssemblyName assemblyName)
|
|
||||||
{
|
|
||||||
if (assemblyName == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(assemblyName));
|
|
||||||
}
|
|
||||||
|
|
||||||
var exportedTypeInfos = GetExportedTypes(assemblyName);
|
|
||||||
|
|
||||||
return exportedTypeInfos
|
|
||||||
.Where(typeInfo => !typeInfo.IsNested)
|
|
||||||
.Select(typeInfo => new RuntimeTypeInfo(typeInfo));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -105,12 +84,14 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal for testing.
|
// Internal for testing.
|
||||||
internal virtual bool IsTagHelper(ITypeInfo typeInfo)
|
internal virtual bool IsTagHelper(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
return typeInfo.IsPublic &&
|
return
|
||||||
!typeInfo.IsAbstract &&
|
!typeInfo.IsNested &&
|
||||||
!typeInfo.IsGenericType &&
|
typeInfo.IsPublic &&
|
||||||
typeInfo.ImplementsInterface(ITagHelperTypeInfo);
|
!typeInfo.IsAbstract &&
|
||||||
|
!typeInfo.IsGenericType &&
|
||||||
|
ITagHelperTypeInfo.IsAssignableFrom(typeInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,160 +0,0 @@
|
||||||
// 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.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
public class RuntimePropertyInfoTest
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public void PropertyInfo_ReturnsMetadataOfAdaptingProperty()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(nameof(TestType.Property));
|
|
||||||
var runtimePropertyInfo = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimePropertyInfo.Property;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Same(property, actual);
|
|
||||||
var runtimeTypeInfo = Assert.IsType<RuntimeTypeInfo>(runtimePropertyInfo.PropertyType);
|
|
||||||
Assert.Same(property.PropertyType, runtimeTypeInfo.TypeInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(nameof(TestType.Property))]
|
|
||||||
[InlineData(nameof(TestType.PrivateSetter))]
|
|
||||||
[InlineData(nameof(TestType.PropertyWithoutSetter))]
|
|
||||||
public void HasPublicGetter_ReturnsTrueIfGetterExistsAndIsPublic(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(propertyName);
|
|
||||||
var runtimePropertyInfo = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = runtimePropertyInfo.HasPublicGetter;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(nameof(TestType.PrivateGetter))]
|
|
||||||
[InlineData(nameof(TestType.PropertyWithoutGetter))]
|
|
||||||
[InlineData("ProtectedProperty")]
|
|
||||||
public void HasPublicGetter_ReturnsFalseIfGetterDoesNotExistOrIsNonPublic(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(propertyName);
|
|
||||||
var runtimePropertyInfo = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = runtimePropertyInfo.HasPublicGetter;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(nameof(TestType.Property))]
|
|
||||||
[InlineData(nameof(TestType.PrivateGetter))]
|
|
||||||
[InlineData(nameof(TestType.PropertyWithoutGetter))]
|
|
||||||
public void HasPublicSetter_ReturnsTrueIfSetterExistsAndIsPublic(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(propertyName);
|
|
||||||
var runtimePropertyInfo = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = runtimePropertyInfo.HasPublicSetter;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(nameof(TestType.PrivateSetter))]
|
|
||||||
[InlineData(nameof(TestType.PropertyWithoutSetter))]
|
|
||||||
[InlineData("ProtectedProperty")]
|
|
||||||
public void HasPublicSetter_ReturnsFalseIfGetterDoesNotExistOrIsNonPublic(string propertyName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(propertyName);
|
|
||||||
var runtimePropertyInfo = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = runtimePropertyInfo.HasPublicSetter;
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetAttributes_ReturnsCustomAttributesOfSpecifiedType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(nameof(TestType.PropertyWithAttributes));
|
|
||||||
var runtimeProperty = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var attributes = property.GetCustomAttributes<HtmlAttributeNameAttribute>();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var htmlAttributeName = Assert.Single(attributes);
|
|
||||||
Assert.Equal("somename", htmlAttributeName.Name);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetAttributes_DoesNotInheritAttributes()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var property = GetPropertyInfo(nameof(TestType.PropertyWithAttributes));
|
|
||||||
var runtimeProperty = new RuntimePropertyInfo(property);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var attributes = property.GetCustomAttributes<HtmlAttributeNotBoundAttribute>();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PropertyInfo GetPropertyInfo(string propertyName)
|
|
||||||
{
|
|
||||||
return typeof(TestType).GetRuntimeProperties()
|
|
||||||
.FirstOrDefault(p => p.Name == propertyName);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseType
|
|
||||||
{
|
|
||||||
[HtmlAttributeNotBound]
|
|
||||||
public virtual string PropertyWithAttributes { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TestType : BaseType
|
|
||||||
{
|
|
||||||
public string Property { get; set; }
|
|
||||||
|
|
||||||
public int PrivateSetter { get; private set; }
|
|
||||||
|
|
||||||
public object PrivateGetter { private get; set; }
|
|
||||||
|
|
||||||
protected DateTimeOffset ProtectedProperty { get; set; }
|
|
||||||
|
|
||||||
public string PropertyWithoutGetter
|
|
||||||
{
|
|
||||||
set { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public int PropertyWithoutSetter => 0;
|
|
||||||
|
|
||||||
[HtmlAttributeName("somename")]
|
|
||||||
public override string PropertyWithAttributes { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,159 +0,0 @@
|
||||||
// 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.Linq;
|
|
||||||
using System.Reflection;
|
|
||||||
using Microsoft.AspNet.Razor.Compilation.TagHelpers;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
|
||||||
using Microsoft.AspNet.Razor.Test.Internal;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
public class RuntimeTagHelperDescriptorFactoryTest : TagHelperDescriptorFactoryTest
|
|
||||||
{
|
|
||||||
public override ITypeInfo GetTypeInfo(Type tagHelperType) =>
|
|
||||||
new RuntimeTypeInfo(tagHelperType.GetTypeInfo());
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void CreateDescriptors_BuildsDescriptorsFromSimpleTypes()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var errorSink = new ErrorSink();
|
|
||||||
var objectAssemblyName = typeof(object).GetTypeInfo().Assembly.GetName().Name;
|
|
||||||
var expectedDescriptor =
|
|
||||||
CreateTagHelperDescriptor("object", "System.Object", objectAssemblyName);
|
|
||||||
var factory = new TagHelperDescriptorFactory(designTime: false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var descriptors = factory.CreateDescriptors(
|
|
||||||
objectAssemblyName,
|
|
||||||
GetTypeInfo(typeof(object)),
|
|
||||||
errorSink: errorSink);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(errorSink.Errors);
|
|
||||||
var descriptor = Assert.Single(descriptors);
|
|
||||||
Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(TagHelperWithPrefixData))]
|
|
||||||
public void CreateDescriptors_WithPrefixes_ReturnsExpectedAttributeDescriptors(
|
|
||||||
Type tagHelperType,
|
|
||||||
IEnumerable<TagHelperAttributeDescriptor> expectedAttributeDescriptors,
|
|
||||||
string[] expectedErrorMessages)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var errorSink = new ErrorSink();
|
|
||||||
var factory = new TagHelperDescriptorFactory(designTime: false);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var descriptors = factory.CreateDescriptors(
|
|
||||||
AssemblyName,
|
|
||||||
GetTypeInfo(tagHelperType),
|
|
||||||
errorSink: errorSink);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
var errors = errorSink.Errors.ToArray();
|
|
||||||
Assert.Equal(expectedErrorMessages.Length, errors.Length);
|
|
||||||
|
|
||||||
for (var i = 0; i < errors.Length; i++)
|
|
||||||
{
|
|
||||||
Assert.Equal(0, errors[i].Length);
|
|
||||||
Assert.Equal(SourceLocation.Zero, errors[i].Location);
|
|
||||||
Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal);
|
|
||||||
}
|
|
||||||
|
|
||||||
var descriptor = Assert.Single(descriptors);
|
|
||||||
Assert.Equal(
|
|
||||||
expectedAttributeDescriptors,
|
|
||||||
descriptor.Attributes,
|
|
||||||
TagHelperAttributeDescriptorComparer.Default);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TagHelperDesignTimeDescriptors are not created in CoreCLR.
|
|
||||||
#if !DNXCORE50
|
|
||||||
public static TheoryData OutputElementHintData
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
// tagHelperType, expectedDescriptors
|
|
||||||
return new TheoryData<Type, TagHelperDescriptor[]>
|
|
||||||
{
|
|
||||||
{
|
|
||||||
typeof(OutputElementHintTagHelper),
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new TagHelperDescriptor
|
|
||||||
{
|
|
||||||
TagName = "output-element-hint",
|
|
||||||
TypeName = typeof(OutputElementHintTagHelper).FullName,
|
|
||||||
AssemblyName = AssemblyName,
|
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
|
||||||
{
|
|
||||||
OutputElementHint = "strong"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
typeof(MulitpleDescriptorTagHelperWithOutputElementHint),
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new TagHelperDescriptor
|
|
||||||
{
|
|
||||||
TagName = "a",
|
|
||||||
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
|
|
||||||
AssemblyName = AssemblyName,
|
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
|
||||||
{
|
|
||||||
OutputElementHint = "div"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new TagHelperDescriptor
|
|
||||||
{
|
|
||||||
TagName = "p",
|
|
||||||
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
|
|
||||||
AssemblyName = AssemblyName,
|
|
||||||
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
|
||||||
{
|
|
||||||
OutputElementHint = "div"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(OutputElementHintData))]
|
|
||||||
public void CreateDescriptors_CreatesDesignTimeDescriptorsWithOutputElementHint(
|
|
||||||
Type tagHelperType,
|
|
||||||
TagHelperDescriptor[] expectedDescriptors)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var errorSink = new ErrorSink();
|
|
||||||
var factory = new TagHelperDescriptorFactory(designTime: true);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var descriptors = factory.CreateDescriptors(
|
|
||||||
AssemblyName,
|
|
||||||
GetTypeInfo(tagHelperType),
|
|
||||||
errorSink: errorSink);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(errorSink.Errors);
|
|
||||||
|
|
||||||
// We don't care about order. Mono returns reflected attributes differently so we need to ensure order
|
|
||||||
// doesn't matter by sorting.
|
|
||||||
descriptors = descriptors.OrderBy(descriptor => descriptor.TagName);
|
|
||||||
|
|
||||||
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,535 +0,0 @@
|
||||||
// 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;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
|
||||||
using Microsoft.AspNet.Testing;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
public class RuntimeTypeInfoTest
|
|
||||||
{
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(int))]
|
|
||||||
[InlineData(typeof(string))]
|
|
||||||
[InlineData(typeof(Tuple<>))]
|
|
||||||
[InlineData(typeof(Tuple<>))]
|
|
||||||
[InlineData(typeof(IDictionary<string, string>))]
|
|
||||||
[InlineData(typeof(IDictionary<string, IDictionary<string, CustomType>>))]
|
|
||||||
[InlineData(typeof(IList<IReadOnlyList<IDictionary<List<string>, Tuple<CustomType, object[]>>>>))]
|
|
||||||
[InlineData(typeof(AbstractType))]
|
|
||||||
[InlineData(typeof(PrivateType))]
|
|
||||||
[InlineData(typeof(KnownKeyDictionary<>))]
|
|
||||||
[InlineData(typeof(KnownKeyDictionary<string>))]
|
|
||||||
public void RuntimeTypeInfo_ReturnsMetadataOfAdaptingType(Type type)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = type.GetTypeInfo();
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
Assert.Same(typeInfo, runtimeTypeInfo.TypeInfo);
|
|
||||||
Assert.Equal(typeInfo.Name, runtimeTypeInfo.Name);
|
|
||||||
Assert.Equal(type.FullName, runtimeTypeInfo.FullName);
|
|
||||||
Assert.Equal(typeInfo.IsAbstract, runtimeTypeInfo.IsAbstract);
|
|
||||||
Assert.Equal(typeInfo.IsGenericType, runtimeTypeInfo.IsGenericType);
|
|
||||||
Assert.Equal(typeInfo.IsPublic, runtimeTypeInfo.IsPublic);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void Properties_ReturnsPublicPropertiesOfAdaptingType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = typeof(SubType).GetTypeInfo();
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
Assert.Collection(runtimeTypeInfo.Properties,
|
|
||||||
property =>
|
|
||||||
{
|
|
||||||
Assert.IsType<RuntimePropertyInfo>(property);
|
|
||||||
Assert.Equal("Property1", property.Name);
|
|
||||||
},
|
|
||||||
property =>
|
|
||||||
{
|
|
||||||
Assert.IsType<RuntimePropertyInfo>(property);
|
|
||||||
Assert.Equal("Property2", property.Name);
|
|
||||||
},
|
|
||||||
property =>
|
|
||||||
{
|
|
||||||
Assert.IsType<RuntimePropertyInfo>(property);
|
|
||||||
Assert.Equal("Property3", property.Name);
|
|
||||||
},
|
|
||||||
property =>
|
|
||||||
{
|
|
||||||
Assert.IsType<RuntimePropertyInfo>(property);
|
|
||||||
Assert.Equal("Property4", property.Name);
|
|
||||||
},
|
|
||||||
property =>
|
|
||||||
{
|
|
||||||
Assert.IsType<RuntimePropertyInfo>(property);
|
|
||||||
Assert.Equal("BaseTypeProperty", property.Name);
|
|
||||||
},
|
|
||||||
property =>
|
|
||||||
{
|
|
||||||
Assert.IsType<RuntimePropertyInfo>(property);
|
|
||||||
Assert.Equal("ProtectedProperty", property.Name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetCustomAttributes_ReturnsAllAttributesOfType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = typeof(TypeWithAttributes).GetTypeInfo();
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
|
|
||||||
var expected = typeInfo.GetCustomAttributes<HtmlTargetElementAttribute>();
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimeTypeInfo.GetCustomAttributes<HtmlTargetElementAttribute>();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(expected, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetCustomAttributes_DoesNotInheritAttributesFromBaseType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = typeof(SubType).GetTypeInfo();
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo);
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimeTypeInfo.GetCustomAttributes<EditorBrowsableAttribute>();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Empty(actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void ImplementsInterface_ThrowsIfArgumentIsNotRuntimeType()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeInfo = new RuntimeTypeInfo(typeof(object).GetTypeInfo());
|
|
||||||
var interfaceTypeInfo = new TestTypeInfo();
|
|
||||||
|
|
||||||
// Act and Assert
|
|
||||||
ExceptionAssert.ThrowsArgument(() => typeInfo.ImplementsInterface(interfaceTypeInfo),
|
|
||||||
"interfaceTypeInfo",
|
|
||||||
$"Argument must be an instance of '{typeof(RuntimeTypeInfo)}'.");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(ITagHelper), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(TagHelper), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(ImplementsITagHelper), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(DerivesFromTagHelper), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(Fake.ImplementsRealITagHelper), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(string), typeof(IEnumerable<char>))]
|
|
||||||
[InlineData(typeof(Dictionary<string, string>), typeof(IDictionary<string, string>))]
|
|
||||||
[InlineData(typeof(List<string>), typeof(IList<string>))]
|
|
||||||
[InlineData(typeof(IList<string>), typeof(IEnumerable<string>))]
|
|
||||||
public void ImplementsInterface_ReturnsTrueIfTypeImplementsInterface(Type runtimeType, Type interfaceType)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(runtimeType.GetTypeInfo());
|
|
||||||
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = runtimeTypeInfo.ImplementsInterface(interfaceTypeInfo);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(string), typeof(object))]
|
|
||||||
[InlineData(typeof(DerivesFromTagHelper), typeof(TagHelper))]
|
|
||||||
[InlineData(typeof(string), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(string), typeof(IList<char>))]
|
|
||||||
[InlineData(typeof(SubType), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(Fake.DoesNotImplementRealITagHelper), typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(IDictionary<,>), typeof(IDictionary<string, string>))]
|
|
||||||
public void ImplementsInterface_ReturnsTrueIfTypeDoesNotImplementInterface(Type runtimeType, Type interfaceType)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(runtimeType.GetTypeInfo());
|
|
||||||
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var result = runtimeTypeInfo.ImplementsInterface(interfaceTypeInfo);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(Dictionary<string, string>), new[] { typeof(string), typeof(string) })]
|
|
||||||
[InlineData(typeof(DerivesFromDictionary), new[] { typeof(int), typeof(object) })]
|
|
||||||
[InlineData(typeof(ImplementsIDictionary), new[] { typeof(List<string>), typeof(string) })]
|
|
||||||
[InlineData(typeof(IDictionary<string, IDictionary<string, CustomType>>),
|
|
||||||
new[] { typeof(string), typeof(IDictionary<string, CustomType>) })]
|
|
||||||
[InlineData(typeof(KnownKeyDictionary<ImplementsIDictionary>),
|
|
||||||
new[] { typeof(string), typeof(ImplementsIDictionary) })]
|
|
||||||
public void GetGenericDictionaryParameters_ReturnsKeyAndValueParameterTypeNames(
|
|
||||||
Type type,
|
|
||||||
Type[] expectedTypes)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(actual,
|
|
||||||
keyType =>
|
|
||||||
{
|
|
||||||
Assert.Equal(new RuntimeTypeInfo(expectedTypes[0].GetTypeInfo()), keyType);
|
|
||||||
},
|
|
||||||
valueType =>
|
|
||||||
{
|
|
||||||
Assert.Equal(new RuntimeTypeInfo(expectedTypes[1].GetTypeInfo()), valueType);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetGenericDictionaryParameters_WorksWhenValueParameterIsOpen()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(typeof(KnownKeyDictionary<>).GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Collection(actual,
|
|
||||||
keyType =>
|
|
||||||
{
|
|
||||||
Assert.Equal(new RuntimeTypeInfo(typeof(string).GetTypeInfo()), keyType);
|
|
||||||
},
|
|
||||||
valueType =>
|
|
||||||
{
|
|
||||||
Assert.Null(valueType);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void GetGenericDictionaryParameters_WorksWhenKeyAndValueParametersAreOpen()
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(typeof(Dictionary<,>).GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Equal(new RuntimeTypeInfo[] { null, null }, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(int))]
|
|
||||||
[InlineData(typeof(string))]
|
|
||||||
[InlineData(typeof(List<string>))]
|
|
||||||
[InlineData(typeof(IDictionary))]
|
|
||||||
[InlineData(typeof(ITagHelper))]
|
|
||||||
public void GetGenericDictionaryParameterNames_ReturnsNullIfTypeDoesNotImplementGenericDictionary(Type type)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var actual = runtimeTypeInfo.GetGenericDictionaryParameters();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.Null(actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(string))]
|
|
||||||
[InlineData(typeof(IDictionary<,>))]
|
|
||||||
[InlineData(typeof(ITagHelper))]
|
|
||||||
[InlineData(typeof(TagHelper))]
|
|
||||||
public void Equals_ReturnsTrueIfTypeInfosAreIdentical(Type type)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeA = new RuntimeTypeInfo(type.GetTypeInfo());
|
|
||||||
var typeB = new RuntimeTypeInfo(type.GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var equals = typeA.Equals(typeB);
|
|
||||||
var hashCodeA = typeA.GetHashCode();
|
|
||||||
var hashCodeB = typeB.GetHashCode();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(equals);
|
|
||||||
Assert.Equal(hashCodeA, hashCodeB);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TheoryData Equals_ReturnsTrueIfTypeFullNamesAreIdenticalData =>
|
|
||||||
new TheoryData<Type, string>
|
|
||||||
{
|
|
||||||
{ typeof(string), typeof(string).FullName },
|
|
||||||
{ typeof(ITagHelper), typeof(ITagHelper).FullName },
|
|
||||||
{ typeof(TagHelper), typeof(TagHelper).FullName },
|
|
||||||
{ typeof(TagHelper), typeof(TagHelper).FullName },
|
|
||||||
{ typeof(IDictionary<,>), typeof(IDictionary<,>).FullName },
|
|
||||||
{
|
|
||||||
typeof(IDictionary<string,string>),
|
|
||||||
typeof(IDictionary<string, string>).FullName
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(Equals_ReturnsTrueIfTypeFullNamesAreIdenticalData))]
|
|
||||||
public void Equals_ReturnsTrueIfTypeInfoNamesAreIdentical(Type type, string fullName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeA = new RuntimeTypeInfo(type.GetTypeInfo());
|
|
||||||
var typeB = new TestTypeInfo
|
|
||||||
{
|
|
||||||
FullName = fullName
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var equals = typeA.Equals(typeB);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.True(equals);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[InlineData(typeof(string), typeof(object))]
|
|
||||||
[InlineData(typeof(IDictionary<,>), typeof(IDictionary<string, string>))]
|
|
||||||
[InlineData(typeof(KnownKeyDictionary<string>), typeof(IDictionary<string, string>))]
|
|
||||||
[InlineData(typeof(ITagHelper), typeof(TagHelper))]
|
|
||||||
public void Equals_ReturnsFalseIfTypeInfosAreDifferent(Type typeA, Type typeB)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeAInfo = new RuntimeTypeInfo(typeA.GetTypeInfo());
|
|
||||||
var typeBInfo = new RuntimeTypeInfo(typeB.GetTypeInfo());
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var equals = typeAInfo.Equals(typeBInfo);
|
|
||||||
var hashCodeA = typeAInfo.GetHashCode();
|
|
||||||
var hashCodeB = typeBInfo.GetHashCode();
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(equals);
|
|
||||||
Assert.NotEqual(hashCodeA, hashCodeB);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Theory]
|
|
||||||
[MemberData(nameof(Equals_ReturnsTrueIfTypeFullNamesAreIdenticalData))]
|
|
||||||
public void Equals_ReturnsFalseIfTypeInfoNamesAreDifferent(Type type, string fullName)
|
|
||||||
{
|
|
||||||
// Arrange
|
|
||||||
var typeA = new RuntimeTypeInfo(type.GetTypeInfo());
|
|
||||||
var typeB = new TestTypeInfo
|
|
||||||
{
|
|
||||||
FullName = "Different" + fullName
|
|
||||||
};
|
|
||||||
|
|
||||||
// Act
|
|
||||||
var equals = typeA.Equals(typeB);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
Assert.False(equals);
|
|
||||||
}
|
|
||||||
|
|
||||||
public class AbstractType
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class InternalType
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PrivateType
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
|
||||||
private class BaseType
|
|
||||||
{
|
|
||||||
public string this[string key]
|
|
||||||
{
|
|
||||||
get { return ""; }
|
|
||||||
set { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public string BaseTypeProperty { get; set; }
|
|
||||||
|
|
||||||
protected int ProtectedProperty { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SubType : BaseType
|
|
||||||
{
|
|
||||||
public string Property1 { get; set; }
|
|
||||||
|
|
||||||
public int Property2 { get; }
|
|
||||||
|
|
||||||
public object Property3 { private get; set; }
|
|
||||||
|
|
||||||
private int Property4 { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
[HtmlTargetElement("test1")]
|
|
||||||
[HtmlTargetElement("test2")]
|
|
||||||
private class TypeWithAttributes
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DerivesFromTagHelper : TagHelper
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ImplementsITagHelper : ITagHelper
|
|
||||||
{
|
|
||||||
public int Order { get; } = 0;
|
|
||||||
|
|
||||||
public void Init(TagHelperContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class DerivesFromDictionary : Dictionary<int, object>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ImplementsIDictionary : IDictionary<List<string>, string>, IReadOnlyList<int>
|
|
||||||
{
|
|
||||||
public int this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public string this[List<string> key]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
set
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int Count
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsReadOnly
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICollection<List<string>> Keys
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ICollection<string> Values
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(KeyValuePair<List<string>, string> item)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(List<string> key, string value)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Clear()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Contains(KeyValuePair<List<string>, string> item)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ContainsKey(List<string> key)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CopyTo(KeyValuePair<List<string>, string>[] array, int arrayIndex)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerator<KeyValuePair<List<string>, string>> GetEnumerator()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(KeyValuePair<List<string>, string> item)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Remove(List<string> key)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TryGetValue(List<string> key, out string value)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator<int> IEnumerable<int>.GetEnumerator()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator IEnumerable.GetEnumerator()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class KnownKeyDictionary<TValue> : Dictionary<string, TValue>
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
private class CustomType
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -12,7 +12,7 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
{
|
{
|
||||||
public abstract class TagHelperDescriptorFactoryTest
|
public class TagHelperDescriptorFactoryTest
|
||||||
{
|
{
|
||||||
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
|
protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly =
|
||||||
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName();
|
typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName();
|
||||||
|
|
@ -93,7 +93,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -252,7 +252,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract ITypeInfo GetTypeInfo(Type tagHelperType);
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(RestrictChildrenData))]
|
[MemberData(nameof(RestrictChildrenData))]
|
||||||
|
|
@ -267,7 +266,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -356,7 +355,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -592,7 +591,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink);
|
errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -794,7 +793,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -843,7 +842,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(tagHelperType),
|
tagHelperType,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -880,7 +879,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(OverriddenAttributeTagHelper)),
|
typeof(OverriddenAttributeTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -914,7 +913,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(InheritedOverriddenAttributeTagHelper)),
|
typeof(InheritedOverriddenAttributeTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -948,7 +947,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(InheritedNotOverriddenAttributeTagHelper)),
|
typeof(InheritedNotOverriddenAttributeTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Empty(errorSink.Errors);
|
Assert.Empty(errorSink.Errors);
|
||||||
|
|
@ -978,7 +977,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(InheritedSingleAttributeTagHelper)),
|
typeof(InheritedSingleAttributeTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1006,7 +1005,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(SingleAttributeTagHelper)),
|
typeof(SingleAttributeTagHelper),
|
||||||
errorSink: new ErrorSink());
|
errorSink: new ErrorSink());
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1035,7 +1034,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(MissingAccessorTagHelper)),
|
typeof(MissingAccessorTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1064,7 +1063,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(NonPublicAccessorTagHelper)),
|
typeof(NonPublicAccessorTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1096,7 +1095,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(NotBoundAttributeTagHelper)),
|
typeof(NotBoundAttributeTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1115,7 +1114,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(DuplicateAttributeNameTagHelper)),
|
typeof(DuplicateAttributeNameTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1164,7 +1163,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(MultiTagTagHelper)),
|
typeof(MultiTagTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1196,7 +1195,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(InheritedMultiTagTagHelper)),
|
typeof(InheritedMultiTagTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1226,7 +1225,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(DuplicateTagNameTagHelper)),
|
typeof(DuplicateTagNameTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1256,7 +1255,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(typeof(OverrideNameTagHelper)),
|
typeof(OverrideNameTagHelper),
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1444,7 +1443,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var descriptors = factory.CreateDescriptors(
|
var descriptors = factory.CreateDescriptors(
|
||||||
AssemblyName,
|
AssemblyName,
|
||||||
GetTypeInfo(type),
|
type,
|
||||||
errorSink: errorSink);
|
errorSink: errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1694,7 +1693,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
||||||
descriptor,
|
descriptor,
|
||||||
GetTypeInfo(typeof(MultiTagTagHelper)),
|
typeof(MultiTagTagHelper),
|
||||||
errorSink);
|
errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1736,7 +1735,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
||||||
descriptor,
|
descriptor,
|
||||||
GetTypeInfo(typeof(MultiTagTagHelper)),
|
typeof(MultiTagTagHelper),
|
||||||
errorSink);
|
errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1782,7 +1781,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
||||||
descriptor,
|
descriptor,
|
||||||
GetTypeInfo(typeof(MultiTagTagHelper)),
|
typeof(MultiTagTagHelper),
|
||||||
errorSink);
|
errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1837,7 +1836,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
// Act
|
// Act
|
||||||
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
var result = TagHelperDescriptorFactory.ValidateTagHelperAttributeDescriptor(
|
||||||
descriptor,
|
descriptor,
|
||||||
GetTypeInfo(typeof(MultiTagTagHelper)),
|
typeof(MultiTagTagHelper),
|
||||||
errorSink);
|
errorSink);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
@ -1925,6 +1924,145 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Assert.Equal(expectedErrors, errorSink.Errors);
|
Assert.Equal(expectedErrors, errorSink.Errors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CreateDescriptors_BuildsDescriptorsFromSimpleTypes()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var errorSink = new ErrorSink();
|
||||||
|
var objectAssemblyName = typeof(object).GetTypeInfo().Assembly.GetName().Name;
|
||||||
|
var expectedDescriptor =
|
||||||
|
CreateTagHelperDescriptor("object", "System.Object", objectAssemblyName);
|
||||||
|
var factory = new TagHelperDescriptorFactory(designTime: false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptors = factory.CreateDescriptors(
|
||||||
|
objectAssemblyName,
|
||||||
|
typeof(object),
|
||||||
|
errorSink: errorSink);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(errorSink.Errors);
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
|
Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(TagHelperWithPrefixData))]
|
||||||
|
public void CreateDescriptors_WithPrefixes_ReturnsExpectedAttributeDescriptors(
|
||||||
|
Type tagHelperType,
|
||||||
|
IEnumerable<TagHelperAttributeDescriptor> expectedAttributeDescriptors,
|
||||||
|
string[] expectedErrorMessages)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var errorSink = new ErrorSink();
|
||||||
|
var factory = new TagHelperDescriptorFactory(designTime: false);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptors = factory.CreateDescriptors(
|
||||||
|
AssemblyName,
|
||||||
|
tagHelperType,
|
||||||
|
errorSink: errorSink);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var errors = errorSink.Errors.ToArray();
|
||||||
|
Assert.Equal(expectedErrorMessages.Length, errors.Length);
|
||||||
|
|
||||||
|
for (var i = 0; i < errors.Length; i++)
|
||||||
|
{
|
||||||
|
Assert.Equal(0, errors[i].Length);
|
||||||
|
Assert.Equal(SourceLocation.Zero, errors[i].Location);
|
||||||
|
Assert.Equal(expectedErrorMessages[i], errors[i].Message, StringComparer.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
|
var descriptor = Assert.Single(descriptors);
|
||||||
|
Assert.Equal(
|
||||||
|
expectedAttributeDescriptors,
|
||||||
|
descriptor.Attributes,
|
||||||
|
TagHelperAttributeDescriptorComparer.Default);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TagHelperDesignTimeDescriptors are not created in CoreCLR.
|
||||||
|
#if !DNXCORE50
|
||||||
|
public static TheoryData OutputElementHintData
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// tagHelperType, expectedDescriptors
|
||||||
|
return new TheoryData<Type, TagHelperDescriptor[]>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
typeof(OutputElementHintTagHelper),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
TagName = "output-element-hint",
|
||||||
|
TypeName = typeof(OutputElementHintTagHelper).FullName,
|
||||||
|
AssemblyName = AssemblyName,
|
||||||
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
|
{
|
||||||
|
OutputElementHint = "strong"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
typeof(MulitpleDescriptorTagHelperWithOutputElementHint),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
TagName = "a",
|
||||||
|
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
|
||||||
|
AssemblyName = AssemblyName,
|
||||||
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
|
{
|
||||||
|
OutputElementHint = "div"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new TagHelperDescriptor
|
||||||
|
{
|
||||||
|
TagName = "p",
|
||||||
|
TypeName = typeof(MulitpleDescriptorTagHelperWithOutputElementHint).FullName,
|
||||||
|
AssemblyName = AssemblyName,
|
||||||
|
DesignTimeDescriptor = new TagHelperDesignTimeDescriptor
|
||||||
|
{
|
||||||
|
OutputElementHint = "div"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[MemberData(nameof(OutputElementHintData))]
|
||||||
|
public void CreateDescriptors_CreatesDesignTimeDescriptorsWithOutputElementHint(
|
||||||
|
Type tagHelperType,
|
||||||
|
TagHelperDescriptor[] expectedDescriptors)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var errorSink = new ErrorSink();
|
||||||
|
var factory = new TagHelperDescriptorFactory(designTime: true);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var descriptors = factory.CreateDescriptors(
|
||||||
|
AssemblyName,
|
||||||
|
tagHelperType,
|
||||||
|
errorSink: errorSink);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Empty(errorSink.Errors);
|
||||||
|
|
||||||
|
// We don't care about order. Mono returns reflected attributes differently so we need to ensure order
|
||||||
|
// doesn't matter by sorting.
|
||||||
|
descriptors = descriptors.OrderBy(descriptor => descriptor.TagName);
|
||||||
|
|
||||||
|
Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private static TheoryData<string, string[]> GetInvalidNameOrPrefixData(
|
private static TheoryData<string, string[]> GetInvalidNameOrPrefixData(
|
||||||
Func<string, string, string> onNameError,
|
Func<string, string, string> onNameError,
|
||||||
string whitespaceErrorString,
|
string whitespaceErrorString,
|
||||||
|
|
|
||||||
|
|
@ -1414,7 +1414,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
return types?.Select(type => type.GetTypeInfo()) ?? Enumerable.Empty<TypeInfo>();
|
return types?.Select(type => type.GetTypeInfo()) ?? Enumerable.Empty<TypeInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal override bool IsTagHelper(ITypeInfo typeInfo)
|
internal override bool IsTagHelper(TypeInfo typeInfo)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,13 +66,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
|
||||||
Assert.Collection(types,
|
Assert.Collection(types,
|
||||||
type =>
|
type =>
|
||||||
{
|
{
|
||||||
var typeInfo = Assert.IsType<RuntimeTypeInfo>(type);
|
Assert.Equal(typeof(Valid_PlainTagHelper), type);
|
||||||
Assert.Equal(typeof(Valid_PlainTagHelper).GetTypeInfo(), typeInfo.TypeInfo);
|
|
||||||
},
|
},
|
||||||
type =>
|
type =>
|
||||||
{
|
{
|
||||||
var typeInfo = Assert.IsType<RuntimeTypeInfo>(type);
|
Assert.Equal(typeof(Valid_InheritedTagHelper), type);
|
||||||
Assert.Equal(typeof(Valid_InheritedTagHelper).GetTypeInfo(), typeInfo.TypeInfo);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
// 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.AspNet.Razor.Fake
|
|
||||||
{
|
|
||||||
public class DoesNotImplementRealITagHelper : Microsoft.AspNet.Razor.Fake.ITagHelper
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
// 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.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Fake
|
|
||||||
{
|
|
||||||
public interface ITagHelper
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
// 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.Threading.Tasks;
|
|
||||||
using Microsoft.AspNet.Razor.TagHelpers;
|
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Razor.Fake
|
|
||||||
{
|
|
||||||
public class ImplementsRealITagHelper : Microsoft.AspNet.Razor.TagHelpers.ITagHelper
|
|
||||||
{
|
|
||||||
public int Order
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init(TagHelperContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -429,58 +429,4 @@ namespace Microsoft.AspNet.Razor.TagHelpers
|
||||||
public class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper
|
public class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TypeDerivingFromITagHelper : ITagHelper
|
|
||||||
{
|
|
||||||
public int Order { get; } = 0;
|
|
||||||
|
|
||||||
public void Init(TagHelperContext context)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseAttribute : Attribute
|
|
||||||
{
|
|
||||||
public string BaseProperty { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DerivedAttribute : BaseAttribute
|
|
||||||
{
|
|
||||||
public string DerivedProperty { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BaseTagHelper : TagHelper
|
|
||||||
{
|
|
||||||
[Derived(DerivedProperty = "DerivedPropertyValue")]
|
|
||||||
public string BaseProperty { get; set; }
|
|
||||||
|
|
||||||
[HtmlAttributeNotBound]
|
|
||||||
public virtual string VirtualProperty { get; set; }
|
|
||||||
|
|
||||||
public int NewProperty { get; set; }
|
|
||||||
|
|
||||||
public virtual new string Order { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class DerivedTagHelper : BaseTagHelper
|
|
||||||
{
|
|
||||||
public override string VirtualProperty { get; set; }
|
|
||||||
|
|
||||||
[Base(BaseProperty = "BaseValue")]
|
|
||||||
public string DerivedProperty { get; set; }
|
|
||||||
|
|
||||||
[HtmlAttributeName("new-property")]
|
|
||||||
public new Type NewProperty { get; set; }
|
|
||||||
|
|
||||||
public override string Order { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class TagHelperInGlobalNamespace : TagHelper
|
|
||||||
{
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,81 +0,0 @@
|
||||||
// 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.AspNet.Razor.Runtime.TagHelpers
|
|
||||||
{
|
|
||||||
public class TestTypeInfo : ITypeInfo
|
|
||||||
{
|
|
||||||
public string FullName { get; set; }
|
|
||||||
|
|
||||||
public bool IsAbstract
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsGenericType
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsPublic
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsEnum
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ImplementsInterface(ITypeInfo other)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public string Name
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IPropertyInfo> Properties
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool Equals(ITypeInfo other)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<TAttribute> GetCustomAttributes<TAttribute>() where TAttribute : Attribute
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ITypeInfo[] GetGenericDictionaryParameters()
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Reference in New Issue