Change ITypeInfo.IsTagHelper to ITypeInfo.ImplementsInterface(ITypeInfo)

Fixes #511
This commit is contained in:
Pranav K 2015-09-25 15:05:39 -07:00
parent d2a7a355cc
commit e87f10e8f8
13 changed files with 335 additions and 131 deletions

View File

@ -18,13 +18,8 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
[DebuggerDisplay("{Name}")] [DebuggerDisplay("{Name}")]
public class CodeAnalysisSymbolBasedTypeInfo : ITypeInfo public class CodeAnalysisSymbolBasedTypeInfo : ITypeInfo
{ {
/// <summary> private static readonly System.Reflection.TypeInfo OpenGenericDictionaryTypeInfo =
/// The <see cref="System.Reflection.TypeInfo"/> for <see cref="IDictionary{TKey, TValue}"/>.
/// </summary>
public static readonly System.Reflection.TypeInfo OpenGenericDictionaryTypeInfo =
typeof(IDictionary<,>).GetTypeInfo(); typeof(IDictionary<,>).GetTypeInfo();
private static readonly System.Reflection.TypeInfo TagHelperTypeInfo =
typeof(ITagHelper).GetTypeInfo();
private readonly ITypeSymbol _type; private readonly ITypeSymbol _type;
private readonly ITypeSymbol _underlyingType; private readonly ITypeSymbol _underlyingType;
private string _fullName; private string _fullName;
@ -120,13 +115,33 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
} }
/// <inheritdoc /> /// <inheritdoc />
public bool IsTagHelper public bool ImplementsInterface(ITypeInfo interfaceTypeInfo)
{ {
get if (interfaceTypeInfo == null)
{ {
return _type.AllInterfaces.Any( throw new ArgumentNullException(nameof(interfaceTypeInfo));
implementedInterface => IsType(implementedInterface, TagHelperTypeInfo));
} }
var runtimeTypeInfo = interfaceTypeInfo as RuntimeTypeInfo;
if (runtimeTypeInfo != null)
{
return runtimeTypeInfo.TypeInfo.IsInterface &&
_type.AllInterfaces.Any(implementedInterface => IsType(implementedInterface, runtimeTypeInfo.TypeInfo));
}
var codeAnalysisTypeInfo = interfaceTypeInfo as CodeAnalysisSymbolBasedTypeInfo;
if (codeAnalysisTypeInfo != null)
{
return codeAnalysisTypeInfo.TypeSymbol.TypeKind == TypeKind.Interface &&
_type.AllInterfaces.Any(
implementedInterface => implementedInterface == codeAnalysisTypeInfo.TypeSymbol);
}
throw new ArgumentException(
Resources.FormatArgumentMustBeAnInstanceOf(
typeof(RuntimeTypeInfo).FullName,
typeof(CodeAnalysisSymbolBasedTypeInfo).FullName),
nameof(interfaceTypeInfo));
} }
private string SanitizedFullName private string SanitizedFullName
@ -193,8 +208,8 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
System.Reflection.TypeInfo targetTypeInfo) System.Reflection.TypeInfo targetTypeInfo)
{ {
return string.Equals( return string.Equals(
targetTypeInfo.FullName, RuntimeTypeInfo.SanitizeFullName(targetTypeInfo.FullName),
GetFullName(sourceTypeSymbol), RuntimeTypeInfo.SanitizeFullName(GetFullName(sourceTypeSymbol)),
StringComparison.Ordinal); StringComparison.Ordinal);
} }

View File

@ -10,6 +10,22 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
private static readonly ResourceManager _resourceManager private static readonly ResourceManager _resourceManager
= new ResourceManager("Microsoft.AspNet.Razor.Runtime.Precompilation.Resources", typeof(Resources).GetTypeInfo().Assembly); = new ResourceManager("Microsoft.AspNet.Razor.Runtime.Precompilation.Resources", typeof(Resources).GetTypeInfo().Assembly);
/// <summary>
/// Argument must be an instance of '{0}' or '{1}'.
/// </summary>
internal static string ArgumentMustBeAnInstanceOf
{
get { return GetString("ArgumentMustBeAnInstanceOf"); }
}
/// <summary>
/// Argument must be an instance of '{0}' or '{1}'.
/// </summary>
internal static string FormatArgumentMustBeAnInstanceOf(object p0, object p1)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0, p1);
}
/// <summary> /// <summary>
/// Unable to find a suitable constructor for type '{0}'. /// Unable to find a suitable constructor for type '{0}'.
/// </summary> /// </summary>

View File

@ -117,6 +117,9 @@
<resheader name="writer"> <resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader> </resheader>
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
<value>Argument must be an instance of '{0}' or '{1}'.</value>
</data>
<data name="CodeAnalysisConstructorNotFound" xml:space="preserve"> <data name="CodeAnalysisConstructorNotFound" xml:space="preserve">
<value>Unable to find a suitable constructor for type '{0}'.</value> <value>Unable to find a suitable constructor for type '{0}'.</value>
</data> </data>

View File

@ -442,6 +442,22 @@ namespace Microsoft.AspNet.Razor.Runtime
return GetString("TagHelperDescriptorFactory_ParentTag"); return GetString("TagHelperDescriptorFactory_ParentTag");
} }
/// <summary>
/// Argument must be an instance of '{0}'.
/// </summary>
internal static string ArgumentMustBeAnInstanceOf
{
get { return GetString("ArgumentMustBeAnInstanceOf"); }
}
/// <summary>
/// Argument must be an instance of '{0}'.
/// </summary>
internal static string FormatArgumentMustBeAnInstanceOf(object p0)
{
return string.Format(CultureInfo.CurrentCulture, GetString("ArgumentMustBeAnInstanceOf"), p0);
}
private static string GetString(string name, params string[] formatterNames) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

@ -198,4 +198,7 @@
<data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve"> <data name="TagHelperDescriptorFactory_ParentTag" xml:space="preserve">
<value>Parent Tag</value> <value>Parent Tag</value>
</data> </data>
<data name="ArgumentMustBeAnInstanceOf" xml:space="preserve">
<value>Argument must be an instance of '{0}'.</value>
</data>
</root> </root>

View File

@ -49,9 +49,9 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
bool IsGenericType { get; } bool IsGenericType { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the type implements the <see cref="ITagHelper"/> interface. /// Gets a value indicating whether the type implements the <param name="interfaceTypeInfo"/> interface.
/// </summary> /// </summary>
bool IsTagHelper { get; } bool ImplementsInterface(ITypeInfo interfaceTypeInfo);
/// <summary> /// <summary>
/// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of /// Gets the <see cref="ITypeInfo[]"/> for the <c>TKey</c> and <c>TValue</c> parameters of

View File

@ -77,7 +77,23 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
} }
/// <inheritdoc /> /// <inheritdoc />
public bool IsTagHelper => TagHelperTypeInfo.IsAssignableFrom(TypeInfo); 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 private string SanitizedFullName
{ {

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
/// </summary> /// </summary>
public class TagHelperTypeResolver public class TagHelperTypeResolver
{ {
private static readonly TypeInfo ITagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo(); private static readonly ITypeInfo ITagHelperTypeInfo = new RuntimeTypeInfo(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"/>.
@ -109,7 +109,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
return typeInfo.IsPublic && return typeInfo.IsPublic &&
!typeInfo.IsAbstract && !typeInfo.IsAbstract &&
!typeInfo.IsGenericType && !typeInfo.IsGenericType &&
typeInfo.IsTagHelper; typeInfo.ImplementsInterface(ITagHelperTypeInfo);
} }
} }
} }

View File

@ -0,0 +1,38 @@
// 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;
namespace Microsoft.AspNet.Razor.Runtime
{
public class DerivingFromIList : IReadOnlyList<string>
{
public string this[int index]
{
get
{
throw new NotImplementedException();
}
}
public int Count
{
get
{
throw new NotImplementedException();
}
}
public IEnumerator<string> GetEnumerator()
{
throw new NotImplementedException();
}
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotImplementedException();
}
}
}

View File

@ -0,0 +1,11 @@
// 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.Collections.Generic;
namespace Microsoft.AspNet.Razor.Runtime
{
public class DerivingFromList : List<string>
{
}
}

View File

@ -8,6 +8,7 @@ using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.Runtime.TagHelpers;
using Microsoft.AspNet.Testing;
using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp;
using Xunit; using Xunit;
@ -15,7 +16,7 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
{ {
public class PrecompilationTagHelperTypeResolverTest public class PrecompilationTagHelperTypeResolverTest
{ {
private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo(); private static readonly ITypeInfo TagHelperTypeInfo = new RuntimeTypeInfo(typeof(ITagHelper).GetTypeInfo());
[Theory] [Theory]
[InlineData(typeof(TypeDerivingFromITagHelper))] [InlineData(typeof(TypeDerivingFromITagHelper))]
@ -35,6 +36,60 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
AssertEqual(expected, actual); AssertEqual(expected, actual);
} }
[Fact]
public void ImplementsInterface_ThrowsIfPassedInParameterIsNotRuntimeTypeOrCodeAnalysisBasedTypeInfo()
{
// Arrange
var interfaceType = new TestTypeInfo();
var compilation = CompilationUtility.GetCompilation(nameof(DerivingFromList));
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
ExceptionAssert.ThrowsArgument(() => actual.ImplementsInterface(interfaceType),
"interfaceTypeInfo",
$"Argument must be an instance of '{typeof(RuntimeTypeInfo)}' or '{typeof(CodeAnalysisSymbolBasedTypeInfo)}'.");
}
[Theory]
[InlineData(typeof(DerivingFromList))]
[InlineData(typeof(DerivingFromIList))]
public void ImplementsInterface_ReturnsTrueIfTypeDerivesFromInterface(Type expected)
{
// Arrange
var interfaceType = new RuntimeTypeInfo(typeof(IEnumerable<string>).GetTypeInfo());
var compilation = CompilationUtility.GetCompilation(expected.Name);
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes);
Assert.True(actual.ImplementsInterface(interfaceType));
}
[Theory]
[InlineData(typeof(TagHelper))]
[InlineData(typeof(IEnumerable<string>))]
public void ImplementsInterface_ReturnsFalseIfTypeDoesNotDerivesFromInterface(Type interfaceType)
{
// Arrange
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
var compilation = CompilationUtility.GetCompilation("TagHelperDescriptorFactoryTagHelpers");
var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation);
// Act
var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName);
// Assert
var actual = Assert.Single(exportedTypes, type => type.Name == nameof(RequiredParentTagHelper));
Assert.False(actual.ImplementsInterface(interfaceTypeInfo));
}
[Fact] [Fact]
public void PropertyNamesForComplexPropertiesAreGeneratedCorrectly() public void PropertyNamesForComplexPropertiesAreGeneratedCorrectly()
{ {
@ -472,7 +527,7 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
// Assert // Assert
var type = Assert.Single(exportedTypes); var type = Assert.Single(exportedTypes);
Assert.False(type.IsTagHelper); Assert.False(type.ImplementsInterface(TagHelperTypeInfo));
} }
private static void AssertAttributes<TAttribute>( private static void AssertAttributes<TAttribute>(
@ -481,7 +536,7 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
Action<TAttribute, TAttribute> assertItem) Action<TAttribute, TAttribute> assertItem)
where TAttribute : Attribute where TAttribute : Attribute
{ {
AssertAttributes<TAttribute>(expected, actual, assertItem, attributes => attributes); AssertAttributes(expected, actual, assertItem, attributes => attributes);
} }
private static void AssertAttributes<TAttribute>( private static void AssertAttributes<TAttribute>(
@ -519,7 +574,9 @@ namespace Microsoft.AspNet.Razor.Runtime.Precompilation
Assert.Equal(expected.IsPublic, actual.IsPublic); Assert.Equal(expected.IsPublic, actual.IsPublic);
Assert.Equal(expected.IsAbstract, actual.IsAbstract); Assert.Equal(expected.IsAbstract, actual.IsAbstract);
Assert.Equal(expected.IsGenericType, actual.IsGenericType); Assert.Equal(expected.IsGenericType, actual.IsGenericType);
Assert.Equal(expected.IsTagHelper, actual.IsTagHelper); Assert.Equal(
expected.ImplementsInterface(TagHelperTypeInfo),
actual.ImplementsInterface(TagHelperTypeInfo));
Assert.Equal( Assert.Equal(
expected.GetGenericDictionaryParameters(), expected.GetGenericDictionaryParameters(),
actual.GetGenericDictionaryParameters(), actual.GetGenericDictionaryParameters(),

View File

@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Testing;
using Xunit; using Xunit;
namespace Microsoft.AspNet.Razor.Runtime.TagHelpers namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
@ -110,35 +111,58 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
Assert.Empty(actual); Assert.Empty(actual);
} }
[Theory] [Fact]
[InlineData(typeof(ITagHelper))] public void ImplementsInterface_ThrowsIfArgumentIsNotRuntimeType()
[InlineData(typeof(TagHelper))]
[InlineData(typeof(ImplementsITagHelper))]
[InlineData(typeof(DerivesFromTagHelper))]
[InlineData(typeof(Fake.ImplementsRealITagHelper))]
public void IsTagHelper_ReturnsTrueIfTypeImplementsTagHelper(Type type)
{ {
// Arrange // Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo()); 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 // Act
var result = runtimeTypeInfo.IsTagHelper; var result = runtimeTypeInfo.ImplementsInterface(interfaceTypeInfo);
// Assert // Assert
Assert.True(result); Assert.True(result);
} }
[Theory] [Theory]
[InlineData(typeof(string))] [InlineData(typeof(string), typeof(object))]
[InlineData(typeof(SubType))] [InlineData(typeof(DerivesFromTagHelper), typeof(TagHelper))]
[InlineData(typeof(Fake.DoesNotImplementRealITagHelper))] [InlineData(typeof(string), typeof(ITagHelper))]
public void IsTagHelper_ReturnsFalseIfTypeDoesNotImplementTagHelper(Type type) [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 // Arrange
var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo()); var runtimeTypeInfo = new RuntimeTypeInfo(runtimeType.GetTypeInfo());
var interfaceTypeInfo = new RuntimeTypeInfo(interfaceType.GetTypeInfo());
// Act // Act
var result = runtimeTypeInfo.IsTagHelper; var result = runtimeTypeInfo.ImplementsInterface(interfaceTypeInfo);
// Assert // Assert
Assert.False(result); Assert.False(result);
@ -502,73 +526,5 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers
private class CustomType private class CustomType
{ {
} }
private 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 IsTagHelper
{
get
{
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();
}
}
} }
} }

View File

@ -0,0 +1,73 @@
// 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 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();
}
}
}