diff --git a/Razor.sln b/Razor.sln index 6a3d5b717a..c08d53743b 100644 --- a/Razor.sln +++ b/Razor.sln @@ -17,6 +17,12 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runt EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Test.Sources", "src\Microsoft.AspNet.Razor.Test.Sources\Microsoft.AspNet.Razor.Test.Sources.xproj", "{E3A2A305-634D-4BA6-95DB-AA06D6C442B0}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.Precompilation", "src\Microsoft.AspNet.Razor.Runtime.Precompilation\Microsoft.AspNet.Razor.Runtime.Precompilation.xproj", "{3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.Precompilation.Test", "test\Microsoft.AspNet.Razor.Runtime.Precompilation.Test\Microsoft.AspNet.Razor.Runtime.Precompilation.Test.xproj", "{C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}" +EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Razor.Runtime.Precompilation.Files", "test\Microsoft.AspNet.Razor.Runtime.Precompilation.Files\Microsoft.AspNet.Razor.Runtime.Precompilation.Files.xproj", "{125D4CC1-5317-495F-88B8-472DFD9C1097}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -43,6 +49,18 @@ Global {E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {E3A2A305-634D-4BA6-95DB-AA06D6C442B0}.Release|Any CPU.Build.0 = Release|Any CPU + {3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B7ECD22-4C02-45CF-92E8-98C074DD9D0E}.Release|Any CPU.Build.0 = Release|Any CPU + {C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C626444C-5A63-42BC-ACAE-DBB2CD3EDE25}.Release|Any CPU.Build.0 = Release|Any CPU + {125D4CC1-5317-495F-88B8-472DFD9C1097}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {125D4CC1-5317-495F-88B8-472DFD9C1097}.Debug|Any CPU.Build.0 = Debug|Any CPU + {125D4CC1-5317-495F-88B8-472DFD9C1097}.Release|Any CPU.ActiveCfg = Release|Any CPU + {125D4CC1-5317-495F-88B8-472DFD9C1097}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -53,5 +71,8 @@ Global {D0196096-1B01-4133-AACE-1A10A0F7247C} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED} {0535998A-E32C-4D1A-80D1-0B15A513C471} = {92463391-81BE-462B-AC3C-78C6C760741F} {E3A2A305-634D-4BA6-95DB-AA06D6C442B0} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED} + {3B7ECD22-4C02-45CF-92E8-98C074DD9D0E} = {3C0D6505-79B3-49D0-B4C3-176F0F1836ED} + {C626444C-5A63-42BC-ACAE-DBB2CD3EDE25} = {92463391-81BE-462B-AC3C-78C6C760741F} + {125D4CC1-5317-495F-88B8-472DFD9C1097} = {92463391-81BE-462B-AC3C-78C6C760741F} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisAttributeUtilities.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisAttributeUtilities.cs new file mode 100644 index 0000000000..36e0b672b1 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisAttributeUtilities.cs @@ -0,0 +1,232 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + /// + /// Utilities to work with creating instances from . + /// + public static class CodeAnalysisAttributeUtilities + { + private static readonly ConcurrentDictionary> _constructorCache = + new ConcurrentDictionary>(); + + /// + /// Gets the sequence of s of type + /// that are declared on the specified . + /// + /// The type. + /// The to find attributes on. + /// The . + /// + public static IEnumerable GetCustomAttributes( + [NotNull] ISymbol symbol, + [NotNull] CodeAnalysisSymbolLookupCache symbolLookup) + where TAttribute : Attribute + { + var attributes = symbol.GetAttributes(); + if (attributes.Length > 0) + { + var attributeSymbol = symbolLookup.GetSymbol(typeof(TAttribute).GetTypeInfo()); + return attributes + .Where(attribute => attribute.AttributeClass == attributeSymbol) + .Select(attribute => CreateAttribute(attribute, symbolLookup)) + .ToArray(); + } + + return Enumerable.Empty(); + } + + private static TAttribute CreateAttribute( + AttributeData attributeData, + CodeAnalysisSymbolLookupCache symbolLookup) + where TAttribute : Attribute + { + TAttribute attribute; + var matchInfo = MatchConstructor(typeof(TAttribute), attributeData.ConstructorArguments, symbolLookup); + Func constructorDelegate; + if (!_constructorCache.TryGetValue(matchInfo.Constructor, out constructorDelegate)) + { + constructorDelegate = MakeFastConstructorInvoker(matchInfo); + _constructorCache[matchInfo.Constructor] = constructorDelegate; + } + + attribute = (TAttribute)constructorDelegate(matchInfo.ArgumentValues); + + if (attributeData.NamedArguments.Length > 0) + { + var helpers = PropertyHelper.GetVisibleProperties(attribute); + foreach (var item in attributeData.NamedArguments) + { + var helper = helpers.FirstOrDefault( + propertyHelper => string.Equals(propertyHelper.Name, item.Key, StringComparison.Ordinal)); + + if (helper == null) + { + throw new InvalidOperationException( + Resources.FormatCodeAnalysis_PropertyNotFound(item.Key, attribute.GetType().FullName)); + } + + var propertyValue = ConvertTypedConstantValue( + helper.Property.PropertyType, + item.Value, + symbolLookup); + helper.SetValue(attribute, propertyValue); + } + } + + return attribute; + } + + private static Func MakeFastConstructorInvoker(ConstructorMatchInfo matchInfo) + { + var argsParameter = Expression.Parameter(typeof(object[]), "args"); + var parameters = new Expression[matchInfo.ArgumentValues.Length]; + + for (var index = 0; index < matchInfo.ArgumentValues.Length; index++) + { + parameters[index] = + Expression.Convert( + Expression.ArrayIndex( + argsParameter, + Expression.Constant(index)), + matchInfo.ArgumentValues[index].GetType()); + } + + // () => new TAttribute(args) + var lambda = + Expression.Lambda>( + Expression.New( + matchInfo.Constructor, + parameters), + argsParameter); + + return lambda.Compile(); + } + + private static ConstructorMatchInfo MatchConstructor( + Type type, + ImmutableArray symbolConstructorArguments, + CodeAnalysisSymbolLookupCache symbolLookup) + { + var constructor = FindConstructor(type, symbolConstructorArguments, symbolLookup); + var constructorParmaters = constructor.GetParameters(); + + var arguments = new object[symbolConstructorArguments.Length]; + for (var i = 0; i < arguments.Length; i++) + { + var value = ConvertTypedConstantValue( + constructorParmaters[i].ParameterType, + symbolConstructorArguments[i], + symbolLookup); + + arguments[i] = value; + } + + return new ConstructorMatchInfo + { + Constructor = constructor, + ArgumentValues = arguments + }; + } + + private static ConstructorInfo FindConstructor( + Type type, + ImmutableArray symbolConstructorArguments, + CodeAnalysisSymbolLookupCache symbolLookup) + { + var constructors = type.GetConstructors(); + foreach (var constructor in constructors) + { + var runtimeParameters = constructor.GetParameters(); + if (runtimeParameters.Length != symbolConstructorArguments.Length) + { + continue; + } + + var parametersMatched = true; + for (var index = 0; index < runtimeParameters.Length; index++) + { + var runtimeParameter = runtimeParameters[index].ParameterType; + if (symbolConstructorArguments[index].Kind == TypedConstantKind.Array && + runtimeParameter.IsArray) + { + var arrayType = (IArrayTypeSymbol)symbolConstructorArguments[index].Type; + if (symbolLookup.GetSymbol(runtimeParameter.GetElementType().GetTypeInfo()) != + arrayType.ElementType) + { + parametersMatched = false; + break; + } + } + else + { + var parameterSymbol = symbolLookup.GetSymbol(runtimeParameter.GetTypeInfo()); + if (symbolConstructorArguments[index].Type != parameterSymbol) + { + parametersMatched = false; + break; + } + } + } + + if (parametersMatched) + { + return constructor; + } + } + + throw new InvalidOperationException(Resources.FormatCodeAnalysisConstructorNotFound(type.FullName)); + } + + private static object ConvertTypedConstantValue( + Type type, + TypedConstant constructorArgument, + CodeAnalysisSymbolLookupCache symbolLookup) + { + switch (constructorArgument.Kind) + { + case TypedConstantKind.Enum: + return Enum.ToObject(type, constructorArgument.Value); + case TypedConstantKind.Primitive: + return constructorArgument.Value; + case TypedConstantKind.Type: + var typeSymbol = (INamedTypeSymbol)constructorArgument.Value; + var typeName = CodeAnalysisSymbolBasedTypeInfo.GetAssemblyQualifiedName(typeSymbol); + return Type.GetType(typeName); + case TypedConstantKind.Array: + Debug.Assert(type.IsArray && constructorArgument.Values != null); + var elementType = type.GetElementType(); + var values = Array.CreateInstance(elementType, constructorArgument.Values.Length); + for (var index = 0; index < values.Length; index++) + { + values.SetValue( + ConvertTypedConstantValue(elementType, constructorArgument.Values[index], symbolLookup), + index); + } + return values; + default: + throw new NotSupportedException( + Resources.FormatCodeAnalysis_TypeConstantKindNotSupported(constructorArgument.Kind)); + } + } + + private struct ConstructorMatchInfo + { + public ConstructorInfo Constructor; + + public object[] ArgumentValues; + } + } +} diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolBasedPropertyInfo.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolBasedPropertyInfo.cs new file mode 100644 index 0000000000..55bf89a154 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolBasedPropertyInfo.cs @@ -0,0 +1,69 @@ +// 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.Diagnostics; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + /// + /// implementation using Code Analysis symbols. + /// + [DebuggerDisplay("{Name, PropertyType}")] + public class CodeAnalysisSymbolBasedPropertyInfo : IPropertyInfo + { + private readonly IPropertySymbol _propertySymbol; + private readonly CodeAnalysisSymbolLookupCache _symbolLookup; + + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + public CodeAnalysisSymbolBasedPropertyInfo( + [NotNull] IPropertySymbol propertySymbol, + [NotNull] CodeAnalysisSymbolLookupCache symbolLookup) + { + _symbolLookup = symbolLookup; + _propertySymbol = propertySymbol; + PropertyType = new CodeAnalysisSymbolBasedTypeInfo(_propertySymbol.Type, _symbolLookup); + } + + /// + public bool HasPublicGetter + { + get + { + return _propertySymbol.GetMethod != null && + _propertySymbol.GetMethod.DeclaredAccessibility == Accessibility.Public; + } + } + + /// + public bool HasPublicSetter + { + get + { + return _propertySymbol.SetMethod != null && + _propertySymbol.SetMethod.DeclaredAccessibility == Accessibility.Public; + } + } + + /// + public string Name => _propertySymbol.MetadataName; + + /// + public ITypeInfo PropertyType { get; } + + /// + public IEnumerable GetCustomAttributes() + where TAttribute : Attribute + { + return CodeAnalysisAttributeUtilities.GetCustomAttributes(_propertySymbol, _symbolLookup); + } + } +} diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolBasedTypeInfo.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolBasedTypeInfo.cs new file mode 100644 index 0000000000..b5f2364aa8 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolBasedTypeInfo.cs @@ -0,0 +1,327 @@ +// 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.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + /// + /// implementation using Code Analysis symbols. + /// + [DebuggerDisplay("{Name}")] + public class CodeAnalysisSymbolBasedTypeInfo : ITypeInfo + { + /// + /// The for . + /// + public static readonly System.Reflection.TypeInfo OpenGenericDictionaryTypeInfo = + typeof(IDictionary<,>).GetTypeInfo(); + private readonly CodeAnalysisSymbolLookupCache _symbolLookup; + private readonly ITypeSymbol _type; + private readonly ITypeSymbol _underlyingType; + private string _fullName; + private List _properties; + + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + public CodeAnalysisSymbolBasedTypeInfo( + [NotNull] ITypeSymbol type, + [NotNull] CodeAnalysisSymbolLookupCache symbolLookup) + { + _symbolLookup = symbolLookup; + _type = type; + _underlyingType = UnwrapArrayType(type); + } + + /// + public string FullName + { + get + { + if (_fullName == null) + { + _fullName = GetFullName(_type); + } + + return _fullName; + } + } + + /// + /// The instance. + /// + public ITypeSymbol TypeSymbol => _type; + + /// + public bool IsAbstract => _type.IsAbstract; + + /// + public bool IsGenericType + { + get + { + return _type.Kind == SymbolKind.NamedType && + ((INamedTypeSymbol)_type).IsGenericType; + } + } + + /// + public bool IsNested => _underlyingType.ContainingType != null; + + /// + public bool IsPublic + { + get + { + return _type.DeclaredAccessibility == Accessibility.Public || + _type.TypeKind == TypeKind.Array; + } + } + + /// + public string Name + { + get + { + if (_type.TypeKind == TypeKind.Array) + { + return _underlyingType.MetadataName + "[]"; + } + + return _type.MetadataName; + } + } + + /// + public IEnumerable Properties + { + get + { + if (_properties == null) + { + _properties = GetProperties(_type, _symbolLookup); + } + + return _properties; + } + } + + /// + public bool IsTagHelper + { + get + { + var interfaceSymbol = _symbolLookup.GetSymbol(typeof(ITagHelper).GetTypeInfo()); + return _type.AllInterfaces.Any(implementedInterface => implementedInterface == interfaceSymbol); + } + } + + /// + public IEnumerable GetCustomAttributes() + where TAttribute : Attribute + { + return CodeAnalysisAttributeUtilities.GetCustomAttributes(_type, _symbolLookup); + } + + /// + public ITypeInfo[] GetGenericDictionaryParameters() + { + var dictionarySymbol = _symbolLookup.GetSymbol(OpenGenericDictionaryTypeInfo); + + INamedTypeSymbol dictionaryInterface; + if (_type.Kind == SymbolKind.NamedType && + ((INamedTypeSymbol)_type).ConstructedFrom == dictionarySymbol) + { + dictionaryInterface = (INamedTypeSymbol)_type; + } + else + { + dictionaryInterface = _type + .AllInterfaces + .FirstOrDefault(implementedInterface => implementedInterface.ConstructedFrom == dictionarySymbol); + } + + if (dictionaryInterface != null) + { + Debug.Assert(dictionaryInterface.TypeArguments.Length == 2); + + return new[] + { + new CodeAnalysisSymbolBasedTypeInfo(dictionaryInterface.TypeArguments[0], _symbolLookup), + new CodeAnalysisSymbolBasedTypeInfo(dictionaryInterface.TypeArguments[1], _symbolLookup), + }; + } + + return null; + } + + /// + /// Gets the assembly qualified named of the specified . + /// + /// The to generate the name for. + /// The assembly qualified name. + public static string GetAssemblyQualifiedName([NotNull] ITypeSymbol symbol) + { + var builder = new StringBuilder(); + GetAssemblyQualifiedName(builder, symbol); + + return builder.ToString(); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ITypeInfo); + } + + /// + public bool Equals(ITypeInfo other) + { + if (other == null) + { + return false; + } + + var otherSymbolBasedType = other as CodeAnalysisSymbolBasedTypeInfo; + if (otherSymbolBasedType != null) + { + return otherSymbolBasedType.TypeSymbol == TypeSymbol; + } + + return string.Equals(FullName, other.FullName, StringComparison.Ordinal); + } + + /// + public override int GetHashCode() => FullName.GetHashCode(); + + private static List GetProperties( + ITypeSymbol typeSymbol, + CodeAnalysisSymbolLookupCache symbolLookup) + { + var properties = new List(); + var overridenProperties = new HashSet(); + + do + { + foreach (var member in typeSymbol.GetMembers().Where(m => m.Kind == SymbolKind.Property)) + { + var propertySymbol = (IPropertySymbol)member; + if (!propertySymbol.IsIndexer && !overridenProperties.Contains(propertySymbol)) + { + var propertyInfo = new CodeAnalysisSymbolBasedPropertyInfo(propertySymbol, symbolLookup); + properties.Add(propertyInfo); + } + + if (propertySymbol.IsOverride) + { + overridenProperties.Add(propertySymbol.OverriddenProperty); + } + } + + typeSymbol = typeSymbol.BaseType; + + } while (typeSymbol != null); + + return properties; + } + + private static string GetFullName(ITypeSymbol typeSymbol) + { + var nameBuilder = new StringBuilder(); + GetFullName(nameBuilder, typeSymbol); + + return nameBuilder.Length == 0 ? null : nameBuilder.ToString(); + } + + private static void GetFullName(StringBuilder nameBuilder, ITypeSymbol typeSymbol) + { + if (typeSymbol.Kind == SymbolKind.TypeParameter) + { + return; + } + + var insertIndex = nameBuilder.Length; + if (typeSymbol.TypeKind == TypeKind.Array) + { + var arrayType = (IArrayTypeSymbol)typeSymbol; + GetFullName(nameBuilder, arrayType.ElementType); + nameBuilder.Append("[]"); + return; + } + + nameBuilder.Append(typeSymbol.MetadataName); + if (typeSymbol.Kind == SymbolKind.NamedType) + { + var namedSymbol = (INamedTypeSymbol)typeSymbol; + // The symbol represents a generic but not open generic type + if (namedSymbol.IsGenericType && + namedSymbol.ConstructedFrom != namedSymbol) + { + nameBuilder.Append('['); + foreach (var typeArgument in namedSymbol.TypeArguments) + { + nameBuilder.Append('['); + GetFullName(nameBuilder, typeArgument); + nameBuilder.Append("],"); + } + + // Removing trailing slash + Debug.Assert(nameBuilder.Length > 0 && nameBuilder[nameBuilder.Length - 1] == ','); + nameBuilder.Length--; + nameBuilder.Append("]"); + } + } + + var containingType = typeSymbol.ContainingType; + while (containingType != null) + { + nameBuilder + .Insert(insertIndex, '+') + .Insert(insertIndex, containingType.MetadataName); + + containingType = containingType.ContainingType; + } + + var containingNamespace = typeSymbol.ContainingNamespace; + while (!containingNamespace.IsGlobalNamespace) + { + nameBuilder + .Insert(insertIndex, '.') + .Insert(insertIndex, containingNamespace.MetadataName); + + containingNamespace = containingNamespace.ContainingNamespace; + } + } + + private static void GetAssemblyQualifiedName(StringBuilder builder, ITypeSymbol typeSymbol) + { + GetFullName(builder, typeSymbol); + typeSymbol = UnwrapArrayType(typeSymbol); + + builder + .Append(", ") + .Append(typeSymbol.ContainingAssembly.Identity); + } + + private static ITypeSymbol UnwrapArrayType(ITypeSymbol type) + { + if (type.TypeKind == TypeKind.Array) + { + return ((IArrayTypeSymbol)type).ElementType; + } + + return type; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolLookupCache.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolLookupCache.cs new file mode 100644 index 0000000000..831c606038 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/CodeAnalysisSymbolLookupCache.cs @@ -0,0 +1,62 @@ +// 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; +using Microsoft.CodeAnalysis; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + /// + /// Caches mapping of to . + /// + public class CodeAnalysisSymbolLookupCache + { + private readonly Dictionary _symbolLookup = + new Dictionary(); + private readonly object _lookupLock = new object(); + private readonly Compilation _compilation; + private INamedTypeSymbol _dictionarySymbol; + + /// + /// Initialzes a new instance of . + /// + /// The instance. + public CodeAnalysisSymbolLookupCache([NotNull] Compilation compilation) + { + _compilation = compilation; + } + + /// + /// Gets a that corresponds to . + /// + /// The to lookup. + /// + public INamedTypeSymbol GetSymbol([NotNull] System.Reflection.TypeInfo typeInfo) + { + if (typeInfo == CodeAnalysisSymbolBasedTypeInfo.OpenGenericDictionaryTypeInfo) + { + if (_dictionarySymbol == null) + { + // Allow safe races + _dictionarySymbol = _compilation.GetTypeByMetadataName( + CodeAnalysisSymbolBasedTypeInfo.OpenGenericDictionaryTypeInfo.FullName); + } + + return _dictionarySymbol; + } + + lock (_lookupLock) + { + INamedTypeSymbol typeSymbol; + if (!_symbolLookup.TryGetValue(typeInfo, out typeSymbol)) + { + typeSymbol = _compilation.GetTypeByMetadataName(typeInfo.FullName); + _symbolLookup[typeInfo] = typeSymbol; + } + + return typeSymbol; + } + } + } +} diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Microsoft.AspNet.Razor.Runtime.Precompilation.xproj b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Microsoft.AspNet.Razor.Runtime.Precompilation.xproj new file mode 100644 index 0000000000..db248e9e22 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Microsoft.AspNet.Razor.Runtime.Precompilation.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 3b7ecd22-4c02-45cf-92e8-98c074dd9d0e + Microsoft.AspNet.Razor.Runtime.Precompilation + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/PrecompilationTagHelperTypeResolver.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/PrecompilationTagHelperTypeResolver.cs new file mode 100644 index 0000000000..6d3252c5ca --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/PrecompilationTagHelperTypeResolver.cs @@ -0,0 +1,110 @@ +// 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; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.CodeAnalysis; +using Microsoft.Framework.Internal; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + /// + /// used during Razor precompilation. + /// + public class PrecompilationTagHelperTypeResolver : TagHelperTypeResolver + { + private readonly object _assemblyLookupLock = new object(); + private readonly Dictionary> _assemblyLookup + = new Dictionary>(StringComparer.Ordinal); + private readonly Compilation _compilation; + private readonly CodeAnalysisSymbolLookupCache _symbolLookup; + + /// + /// Initializes a new instance of . + /// + /// The . + public PrecompilationTagHelperTypeResolver([NotNull] Compilation compilation) + { + _compilation = compilation; + _symbolLookup = new CodeAnalysisSymbolLookupCache(compilation); + } + + /// + protected override IEnumerable GetTopLevelExportedTypes([NotNull] AssemblyName assemblyName) + { + lock (_assemblyLookupLock) + { + IEnumerable result; + if (!_assemblyLookup.TryGetValue(assemblyName.Name, out result)) + { + result = GetExportedTypes(assemblyName.Name); + _assemblyLookup[assemblyName.Name] = result; + } + + return result; + } + } + + // Internal for unit testing + internal IEnumerable GetExportedTypes(string assemblyName) + { + if (string.Equals(_compilation.AssemblyName, assemblyName, StringComparison.Ordinal)) + { + return GetExportedTypes(_compilation.Assembly); + } + else + { + foreach (var reference in _compilation.References) + { + var compilationReference = reference as CompilationReference; + if (compilationReference != null && + string.Equals( + compilationReference.Compilation.AssemblyName, + assemblyName, + StringComparison.Ordinal)) + { + return GetExportedTypes(compilationReference.Compilation.Assembly); + } + + var assemblySymbol = _compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol; + if (string.Equals( + assemblySymbol?.Identity.Name, + assemblyName, + StringComparison.Ordinal)) + { + return GetExportedTypes(assemblySymbol); + } + } + } + + throw new InvalidOperationException( + Resources.FormatCodeAnalysis_UnableToLoadAssemblyReference(assemblyName)); + } + + private List GetExportedTypes(IAssemblySymbol assembly) + { + var exportedTypes = new List(); + GetExportedTypes(assembly.GlobalNamespace, exportedTypes); + return exportedTypes; + } + + private void GetExportedTypes(INamespaceSymbol namespaceSymbol, List exportedTypes) + { + foreach (var type in namespaceSymbol.GetTypeMembers()) + { + if (type.TypeKind == TypeKind.Class && + type.DeclaredAccessibility == Accessibility.Public) + { + exportedTypes.Add(new CodeAnalysisSymbolBasedTypeInfo(type, _symbolLookup)); + } + } + + foreach (var subNamespace in namespaceSymbol.GetNamespaceMembers()) + { + GetExportedTypes(subNamespace, exportedTypes); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1dbf5603d7 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNet.Razor.Runtime.Precompilation.Test")] diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..cf3f1e0542 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Properties/Resources.Designer.cs @@ -0,0 +1,94 @@ +// +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNet.Razor.Runtime.Precompilation.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Unable to find a suitable constructor for type '{0}'. + /// + internal static string CodeAnalysisConstructorNotFound + { + get { return GetString("CodeAnalysisConstructorNotFound"); } + } + + /// + /// Unable to find a suitable constructor for type '{0}'. + /// + internal static string FormatCodeAnalysisConstructorNotFound(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysisConstructorNotFound"), p0); + } + + /// + /// Unable to find property {0} on type {1}. + /// + internal static string CodeAnalysis_PropertyNotFound + { + get { return GetString("CodeAnalysis_PropertyNotFound"); } + } + + /// + /// Unable to find property {0} on type {1}. + /// + internal static string FormatCodeAnalysis_PropertyNotFound(object p0, object p1) + { + return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysis_PropertyNotFound"), p0, p1); + } + + /// + /// The type constant kind '{0}' is not supported. + /// + internal static string CodeAnalysis_TypeConstantKindNotSupported + { + get { return GetString("CodeAnalysis_TypeConstantKindNotSupported"); } + } + + /// + /// The type constant kind '{0}' is not supported. + /// + internal static string FormatCodeAnalysis_TypeConstantKindNotSupported(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysis_TypeConstantKindNotSupported"), p0); + } + + /// + /// Unable to load assembly reference '{0}'. + /// + internal static string CodeAnalysis_UnableToLoadAssemblyReference + { + get { return GetString("CodeAnalysis_UnableToLoadAssemblyReference"); } + } + + /// + /// Unable to load assembly reference '{0}'. + /// + internal static string FormatCodeAnalysis_UnableToLoadAssemblyReference(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("CodeAnalysis_UnableToLoadAssemblyReference"), p0); + } + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Resources.resx b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Resources.resx new file mode 100644 index 0000000000..4942941366 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/Resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unable to find a suitable constructor for type '{0}'. + + + Unable to find property {0} on type {1}. + + + The type constant kind '{0}' is not supported. + + + Unable to load assembly reference '{0}'. + + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime.Precompilation/project.json b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/project.json new file mode 100644 index 0000000000..235b12f3f5 --- /dev/null +++ b/src/Microsoft.AspNet.Razor.Runtime.Precompilation/project.json @@ -0,0 +1,40 @@ +{ + "description": "Supports tag helper resolution during precompilation.", + "version": "4.0.0-*", + "repository": { + "type": "git", + "url": "git://github.com/aspnet/razor" + }, + "compilationOptions": { + "warningsAsErrors": true + }, + "dependencies": { + "Microsoft.Framework.NotNullAttribute.Sources": { + "version": "1.0.0-*", + "type": "build" + }, + "Microsoft.Framework.PropertyActivator.Sources": { + "version": "1.0.0-*", + "type": "build" + }, + "Microsoft.Framework.PropertyHelper.Sources": { + "version": "1.0.0-*", + "type": "build" + }, + "Microsoft.AspNet.Razor.Runtime": "4.0.0-*", + "Microsoft.Dnx.Compilation.CSharp.Abstractions": "1.0.0-*" + }, + "frameworks": { + "dnx451": { + "frameworkAssemblies": { + "System.Runtime": "4.0.0.0" + } + }, + "dnxcore50": { + "dependencies": { + "System.Collections.Concurrent": "4.0.11-*", + "System.Linq.Expressions": "4.0.11-*" + } + } + } +} diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITypeInfo.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITypeInfo.cs index 294f247932..9a4cc17e3b 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITypeInfo.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/ITypeInfo.cs @@ -46,16 +46,16 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers bool IsTagHelper { get; } /// - /// Gets the full names of the parameter types if the type implements + /// Gets the for the TKey and TValue parameters of /// . /// /// - /// The full type names () of TKey and TValue + /// The of TKey and TValue /// parameters if the type implements , otherwise null. /// /// - /// For open generic types, full type names for generic type parameters is null. + /// For open generic types, for generic type parameters is null. /// - string[] GetGenericDictionaryParameterNames(); + ITypeInfo[] GetGenericDictionaryParameters(); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/RuntimeTypeInfo.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/RuntimeTypeInfo.cs index dff34999c0..2dea152e83 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/RuntimeTypeInfo.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/RuntimeTypeInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Text.RegularExpressions; using Microsoft.Framework.Internal; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers @@ -14,6 +15,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers /// 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, + matchTimeout: TimeSpan.FromSeconds(10)); + private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo(); private IEnumerable _properties; @@ -35,7 +41,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers public string Name => TypeInfo.Name; /// - public string FullName => TypeInfo.FullName; + public string FullName => SanitizeFullName(TypeInfo.FullName); /// public bool IsAbstract => TypeInfo.IsAbstract; @@ -72,17 +78,53 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers TypeInfo.GetCustomAttributes(inherit: false); /// - public string[] GetGenericDictionaryParameterNames() + public ITypeInfo[] GetGenericDictionaryParameters() { return ClosedGenericMatcher.ExtractGenericInterface( TypeInfo.AsType(), typeof(IDictionary<,>)) ?.GenericTypeArguments - .Select(type => type.FullName) + .Select(type => type.IsGenericParameter ? null : new RuntimeTypeInfo(type.GetTypeInfo())) .ToArray(); } /// public override string ToString() => TypeInfo.ToString(); + + /// + public override bool Equals(object obj) + { + return Equals(obj as ITypeInfo); + } + + /// + 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(FullName, other.FullName, StringComparison.Ordinal); + } + + /// + public override int GetHashCode() => FullName.GetHashCode(); + + // Internal for unit testing + internal 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); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs index c337ca89fc..55c0543c37 100644 --- a/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs +++ b/src/Microsoft.AspNet.Razor.Runtime/TagHelpers/TagHelperDescriptorFactory.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Reflection; using System.Text.RegularExpressions; using Microsoft.AspNet.Razor.TagHelpers; using Microsoft.Framework.Internal; @@ -27,6 +28,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers private static readonly Regex HtmlCaseRegex = new Regex("(? + { + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/AssemblyWithNonTypes.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/AssemblyWithNonTypes.cs new file mode 100644 index 0000000000..65ebc153cb --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/AssemblyWithNonTypes.cs @@ -0,0 +1,33 @@ +// 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.Precompilation +{ + public struct MyStruct + { + } + + public enum MyEnum + { + } + + public interface MyInterface + { + } + + public abstract class MyAbstractClass + { + } + + public partial class MyPartialClass + { + } + + public partial class MyPartialClass + { + } + + public sealed class MySealedClass + { + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/EnumPropertyAttribute.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/EnumPropertyAttribute.cs new file mode 100644 index 0000000000..392536b08d --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/EnumPropertyAttribute.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + public class EnumPropertyAttribute : Attribute + { + public DayOfWeek DayOfWeek { get; set; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/Microsoft.AspNet.Razor.Runtime.Precompilation.Files.xproj b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/Microsoft.AspNet.Razor.Runtime.Precompilation.Files.xproj new file mode 100644 index 0000000000..66caf3b1d6 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/Microsoft.AspNet.Razor.Runtime.Precompilation.Files.xproj @@ -0,0 +1,19 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 125d4cc1-5317-495f-88b8-472dfd9c1097 + Microsoft.AspNet.Razor.Runtime.Precompilation.Files + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + + 2.0 + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/MultipleConstructorArgumentsAttribute.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/MultipleConstructorArgumentsAttribute.cs new file mode 100644 index 0000000000..6054fff429 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/MultipleConstructorArgumentsAttribute.cs @@ -0,0 +1,24 @@ +// 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; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] + public class MultipleConstructorArgumentsAttribute : Attribute + { + public MultipleConstructorArgumentsAttribute(string firstArgument, string secondArgument, int thirdArgument) + { + FirstArgument = firstArgument; + SecondArgument = secondArgument; + ThirdArgument = thirdArgument; + } + + public string FirstArgument { get; } + + public string SecondArgument { get; } + + public int ThirdArgument { get; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithArrayProperties.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithArrayProperties.cs new file mode 100644 index 0000000000..3d66d09c8a --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithArrayProperties.cs @@ -0,0 +1,21 @@ +// 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.Precompilation +{ + public class TypeWithArrayProperties + { + public MyAbstractClass[] AbstractArray { get; set; } + + public IDisposable[] DisposableArray { get; } + + public ContainerType.NestedType[] NestedArrayType { get; } + + internal InternalType[] InternalArray { get; set; } + + public ICollection>>[] GenericArray { get; set; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithAttributes.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithAttributes.cs new file mode 100644 index 0000000000..8bc006b093 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithAttributes.cs @@ -0,0 +1,52 @@ +// 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.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Resources; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + [TargetElement("img", Attributes = AppendVersionAttributeName + "," + SrcAttributeName)] + [TargetElement("image", Attributes = SrcAttributeName)] + [EditorBrowsable(EditorBrowsableState.Never)] + [EnumProperty(DayOfWeek = DayOfWeek.Friday)] + [CustomValidation(typeof(Validator), "ValidationMethod", ErrorMessageResourceType = typeof(ResourceManager))] + [RestrictChildren("ol", "ul", "li", "dl", "dd")] + [ArrayProperties( + ArrayOfInts = new[] { 7, 8, 9 }, + ArrayOfTypes = new[] { typeof(ITagHelper), typeof(Guid) }, + Days = new[] { DayOfWeek.Saturday })] + public class TypeWithAttributes + { + private const string AppendVersionAttributeName = "asp-append-version"; + private const string SrcAttributeName = "src"; + + [HtmlAttributeName(SrcAttributeName)] + [Required(AllowEmptyStrings = true)] + public string Src { get; set; } + + [HtmlAttributeName(AppendVersionAttributeName, DictionaryAttributePrefix = "prefix")] + [HtmlAttributeNotBound] + public bool AppendVersion { get; set; } + + [Required] + [EditorBrowsable(EditorBrowsableState.Advanced)] + [ArrayProperties( + ArrayOfInts = new int[0], + ArrayOfTypes = new[] { typeof(TypeWithAttributes) }, + Days = new[] { DayOfWeek.Sunday, DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Sunday })] + public ViewContext ViewContext { get; set; } + + [AttributesWithArrayConstructorArguments(new[] { 1, 2 }, new[] { typeof(Uri), typeof(IList<>) })] + [AttributesWithArrayConstructorArguments(new[] { "Hello", "world" }, new[] { typeof(List) }, new int[0])] + [AttributesWithArrayConstructorArguments( + new[] { "world", "Hello" }, + new[] { typeof(IDictionary) }, + new[] { 1 })] + protected IEditableObject HostingEnvironment { get; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithComplexPropertyFullNames.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithComplexPropertyFullNames.cs new file mode 100644 index 0000000000..0928a974ae --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithComplexPropertyFullNames.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + public class TypeWithComplexPropertyFullNames + { + public int Property1 { get; set; } + + public int[] Property2 { get; set; } + + public List Property3 { get; set; } + + public List> Property4 { get; } + + public IDictionary, IList>> Property5 { get; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithDictionaryProperties.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithDictionaryProperties.cs new file mode 100644 index 0000000000..c421c2fae0 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithDictionaryProperties.cs @@ -0,0 +1,30 @@ +// 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.Precompilation +{ + public class TypeWithDictionaryProperties + { + public IDictionary RouteValues1 { get; set; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + public Dictionary RouteValues2 { get; set; } = + new Dictionary(); + + public Dictionary, float> RouteValues3 { get; set; } = + new Dictionary, float>(); + + public IDictionary CustomDictionary { get; set; } = + new Dictionary(); + + public IDictionary NonGenericDictionary { get; set; } = + new Dictionary(); + + public object ObjectType { get; set; } = + new Dictionary(); + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithNamedAttributes.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithNamedAttributes.cs new file mode 100644 index 0000000000..45e93cf705 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypeWithNamedAttributes.cs @@ -0,0 +1,12 @@ +// 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.Precompilation +{ + [MultipleConstructorArguments(firstArgument: "First1", secondArgument: "Second1", thirdArgument: 31)] + [MultipleConstructorArguments(secondArgument: "Second2", firstArgument: "First2", thirdArgument: 32)] + [MultipleConstructorArguments(thirdArgument: 33, secondArgument: "Second3", firstArgument: "First3")] + public class TypeWithNamedAttributes + { + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypesWithInheritedProperties.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypesWithInheritedProperties.cs new file mode 100644 index 0000000000..950af75e0b --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/TypesWithInheritedProperties.cs @@ -0,0 +1,20 @@ +// 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.Precompilation +{ + public class Animal + { + public virtual string Name { get; set; } + } + + public class Mammal : Animal + { + public override string Name { get; set; } + } + + public class Dog : Mammal + { + public override string Name { get; set; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/ViewContext.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/ViewContext.cs new file mode 100644 index 0000000000..38935bcebe --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/ViewContext.cs @@ -0,0 +1,9 @@ +// 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.Precompilation +{ + public class ViewContext + { + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/project.json b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/project.json new file mode 100644 index 0000000000..df4fcd4086 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Files/project.json @@ -0,0 +1,18 @@ +{ + "version": "1.0.0-*", + "dependencies": { + "Microsoft.AspNet.Razor.Runtime.Precompilation": "4.0.0-*" + }, + "frameworks": { + "dnx451": { + "frameworkAssemblies": { + "System.ComponentModel.DataAnnotations": "4.0.0.0" + } + }, + "dnxcore50": { + "dependencies": { + "System.ComponentModel.Annotations": "4.0.11-*" + } + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMalformedAttribute.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMalformedAttribute.cs new file mode 100644 index 0000000000..b8205b2dca --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMalformedAttribute.cs @@ -0,0 +1,18 @@ +// 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.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Mvc.Razor.Precompilation +{ + [RestrictChildren(Never)] + [CustomValidation(typeof(TypeDoesNotExist)] + [TargetElement("img" + public class TypeWithMalformedAttribute + { + + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMalformedProperties.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMalformedProperties.cs new file mode 100644 index 0000000000..232f7b0437 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMalformedProperties.cs @@ -0,0 +1,16 @@ +// 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; + +namespace Microsoft.AspNet.Mvc.Razor.Precompilation +{ + public class TypeWithMalformedProperties + { + public DateTime DateTime { get } + + public int DateTime2 => "Hello world"; + + public string CustomOrder { get; set; } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMissingReferences.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMissingReferences.cs new file mode 100644 index 0000000000..936e7340e6 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/BadFiles/TypeWithMissingReferences.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.AspNet.Mvc.Razor.Precompilation +{ + public class TypeWithMissingReferences : ITagHelper + { + + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/CompilationUtility.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/CompilationUtility.cs new file mode 100644 index 0000000000..73431f234d --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/CompilationUtility.cs @@ -0,0 +1,86 @@ +// 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.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Dnx.Compilation; +using Microsoft.Dnx.Compilation.CSharp; +using Microsoft.Dnx.Runtime.Infrastructure; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + public static class CompilationUtility + { + private static readonly ConcurrentDictionary _metadataCache = + new ConcurrentDictionary(StringComparer.Ordinal); + private static readonly Assembly ExecutingAssembly = typeof(CompilationUtility).GetTypeInfo().Assembly; + public static readonly string GeneratedAssemblyName = Path.GetRandomFileName() + "." + Path.GetRandomFileName(); + + public static Compilation GetCompilation(params string[] resourceFiles) + { + var assemblyVersion = ExecutingAssembly.GetName().Version; + + var syntaxTrees = new List + { + CSharpSyntaxTree.ParseText( + $"[assembly: {typeof(AssemblyVersionAttribute).FullName}(\"{assemblyVersion}\")]") + }; + + foreach (var resourceFile in resourceFiles) + { + var resourceContent = ReadManifestResource(resourceFile); + syntaxTrees.Add(CSharpSyntaxTree.ParseText(resourceContent)); + } + + var libraryExporter = (ILibraryExporter)CallContextServiceLocator + .Locator + .ServiceProvider + .GetService(typeof(ILibraryExporter)); + var applicationName = ExecutingAssembly.GetName().Name; + var libraryExport = libraryExporter.GetExport(applicationName); + + var references = new List(); + var roslynReference = libraryExport.MetadataReferences[0] as IRoslynMetadataReference; + var compilationReference = roslynReference?.MetadataReference as CompilationReference; + if (compilationReference != null) + { + references.AddRange(compilationReference.Compilation.References); + references.Add(roslynReference.MetadataReference); + } + else + { + var export = libraryExporter.GetAllExports(applicationName); + foreach (var metadataReference in export.MetadataReferences) + { + var reference = metadataReference.ConvertMetadataReference( + fileReference => _metadataCache.GetOrAdd( + fileReference.Path, + _ => fileReference.CreateAssemblyMetadata())); + references.Add(reference); + } + } + + return CSharpCompilation.Create( + GeneratedAssemblyName, + syntaxTrees, + references); + } + + private static string ReadManifestResource(string path) + { + path = $"{ExecutingAssembly.GetName().Name}.{path}.cs"; + using (var contentStream = ExecutingAssembly.GetManifestResourceStream(path)) + { + using (var reader = new StreamReader(contentStream)) + { + return reader.ReadToEnd(); + } + } + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test.xproj b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test.xproj new file mode 100644 index 0000000000..91c07a2eb6 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test.xproj @@ -0,0 +1,21 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + c626444c-5a63-42bc-acae-dbb2cd3ede25 + Microsoft.AspNet.Razor.Runtime.Precompilation.Test + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/PrecompilationTagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/PrecompilationTagHelperDescriptorFactoryTest.cs new file mode 100644 index 0000000000..5389451074 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/PrecompilationTagHelperDescriptorFactoryTest.cs @@ -0,0 +1,31 @@ +// 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 Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.AspNet.Razor.TagHelpers; +using Microsoft.AspNet.Razor.Test.Internal; +using Xunit; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + public class PrecompilationTagHelperDescriptorFactoryTest : TagHelperDescriptorFactoryTest + { + public override ITypeInfo GetTypeInfo(Type tagHelperType) + { + var paths = new[] + { + $"TagHelperDescriptorFactoryTagHelpers", + $"CommonTagHelpers", + }; + + var compilation = CompilationUtility.GetCompilation(paths); + var typeResolver = new PrecompilationTagHelperTypeResolver(compilation); + + return Assert.Single(typeResolver.GetExportedTypes(CompilationUtility.GeneratedAssemblyName), + generatedType => string.Equals(generatedType.FullName, tagHelperType.FullName, StringComparison.Ordinal)); + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/PrecompilationTagHelperTypeResolverTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/PrecompilationTagHelperTypeResolverTest.cs new file mode 100644 index 0000000000..4309006196 --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/PrecompilationTagHelperTypeResolverTest.cs @@ -0,0 +1,554 @@ +// 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.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.CodeAnalysis.CSharp; +using Xunit; + +namespace Microsoft.AspNet.Razor.Runtime.Precompilation +{ + public class PrecompilationTagHelperTypeResolverTest + { + private static readonly TypeInfo TagHelperTypeInfo = typeof(ITagHelper).GetTypeInfo(); + + [Theory] + [InlineData(typeof(TypeDerivingFromITagHelper))] + [InlineData(typeof(AttributeTargetingTagHelper))] + [InlineData(typeof(TagHelperInGlobalNamespace))] + public void TypesReturnedFromGetTopLevelExportedTypes_ReturnsTopLevelTypeInfo(Type expected) + { + // Arrange + 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.FullName == expected.FullName); + AssertEqual(expected, actual); + } + + [Fact] + public void PropertyNamesForComplexPropertiesAreGeneratedCorrectly() + { + // Arrange + var expectedType = typeof(TypeWithComplexPropertyFullNames); + var compilation = CompilationUtility.GetCompilation(expectedType.Name); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var actual = Assert.Single(exportedTypes); + AssertEqual(expectedType, actual); + } + + [Fact] + public void PropertyNamesForArrayPropertiesAreGeneratedCorrectly() + { + // Arrange + var expectedType = typeof(TypeWithArrayProperties); + var compilation = CompilationUtility.GetCompilation(expectedType.Name); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var actual = Assert.Single(exportedTypes); + AssertEqual(expectedType, actual); + } + + [Fact] + public void GetTopLevelExportedTypes_DoesNotReturnNonPublicOrNestedTypes() + { + // Arrange + var compilation = CompilationUtility.GetCompilation("AssemblyWithNonPublicTypes"); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + Assert.Collection(exportedTypes, + typeInfo => + { + AssertEqual(typeof(PublicType), typeInfo); + }, + typeInfo => + { + AssertEqual(typeof(ContainerType), typeInfo); + }, + typeInfo => + { + AssertEqual(typeof(GenericType<>), typeInfo); + }); + } + + [Fact] + public void GetTopLevelExportedTypes_DoesNotReturnValueTypesOrInterfaces() + { + // Arrange + var compilation = CompilationUtility.GetCompilation("AssemblyWithNonTypes"); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + Assert.Collection(exportedTypes, + typeInfo => + { + AssertEqual(typeof(MyAbstractClass), typeInfo); + }, + typeInfo => + { + AssertEqual(typeof(MyPartialClass), typeInfo); + }, + typeInfo => + { + AssertEqual(typeof(MySealedClass), typeInfo); + }); + } + + [Fact] + public void GetExportedTypes_PopulatesAttributes() + { + // Arrange + var expected = typeof(TypeWithAttributes).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); + AssertEqual(expected.AsType(), actual); + + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.Tag, actualAttribute.Tag); + Assert.Equal(expectedAttribute.Attributes, actualAttribute.Attributes); + }); + + // Verify if enum in attribute constructors works. + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.State, actualAttribute.State); + }); + + // Verify if enum in attribute property works. + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.DayOfWeek, actualAttribute.DayOfWeek); + }); + + // Verify if Type in attribute constructor and property works. + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Same(expectedAttribute.ValidatorType, actualAttribute.ValidatorType); + Assert.Equal(expectedAttribute.Method, actualAttribute.Method); + Assert.Same( + expectedAttribute.ErrorMessageResourceType, + actualAttribute.ErrorMessageResourceType); + }); + + // Verify if array arguments work in constructor. + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal( + expectedAttribute.ChildTagNames, + actualAttribute.ChildTagNames); + }); + + // Complex array bindings + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.ArrayOfTypes, actualAttribute.ArrayOfTypes); + Assert.Equal(expectedAttribute.ArrayOfInts, actualAttribute.ArrayOfInts); + Assert.Equal(expectedAttribute.Days, actualAttribute.Days); + }); + + var expectedProperties = expected.DeclaredProperties; + Assert.Collection(actual.Properties, + property => + { + Assert.Equal(nameof(TypeWithAttributes.Src), property.Name); + var expectedProperty = Assert.Single(expectedProperties, p => p.Name == property.Name); + AssertAttributes( + expectedProperty, + property, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.Name, actualAttribute.Name); + Assert.Equal(expectedAttribute.DictionaryAttributePrefix, actualAttribute.DictionaryAttributePrefix); + }); + + // Verify boolean values bind. + AssertAttributes( + expectedProperty, + property, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.AllowEmptyStrings, actualAttribute.AllowEmptyStrings); + }); + }, + property => + { + Assert.Equal(nameof(TypeWithAttributes.AppendVersion), property.Name); + var expectedProperty = Assert.Single(expectedProperties, p => p.Name == property.Name); + AssertAttributes( + expectedProperty, + property, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.Name, actualAttribute.Name); + Assert.Equal(expectedAttribute.DictionaryAttributePrefix, actualAttribute.DictionaryAttributePrefix); + }); + + // Attribute without constructor arguments or properties. + Assert.Single(expectedProperty.GetCustomAttributes()); + Assert.Single(property.GetCustomAttributes()); + }, + property => + { + Assert.Equal(nameof(TypeWithAttributes.ViewContext), property.Name); + var expectedProperty = Assert.Single(expectedProperties, p => p.Name == property.Name); + + AssertAttributes( + expectedProperty, + property, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.State, actualAttribute.State); + }); + + // Complex array bindings in properties + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.ArrayOfTypes, actualAttribute.ArrayOfTypes); + Assert.Equal(expectedAttribute.ArrayOfInts, actualAttribute.ArrayOfInts); + Assert.Equal(expectedAttribute.Days, actualAttribute.Days); + }); + }, + property => + { + Assert.Equal("HostingEnvironment", property.Name); + Assert.Single(expectedProperties, p => p.Name == property.Name); + + // Complex array bindings in constructor arguments + AssertAttributes( + expected, + actual, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.StringArgs, actualAttribute.StringArgs); + Assert.Equal(expectedAttribute.IntArgs, actualAttribute.IntArgs); + Assert.Equal(expectedAttribute.TypeArgs, actualAttribute.TypeArgs); + }); + }); + } + + [Fact] + public void GetExportedTypes_WithDerivedAttributes() + { + // Arrange + var expected = typeof(DerivedTagHelper); + 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 == expected.Name); + AssertEqual(expected, actual); + + var expectedProperties = expected.GetProperties(); + AssertAttributes( + expectedProperties.First(p => p.Name == nameof(DerivedTagHelper.DerivedProperty)), + actual.Properties.First(p => p.Name == nameof(DerivedTagHelper.DerivedProperty)), + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.BaseProperty, actualAttribute.BaseProperty); + }); + + AssertAttributes( + expectedProperties.First(p => p.Name == nameof(DerivedTagHelper.NewProperty) && + p.PropertyType == typeof(Type)), + actual.Properties.First(p => p.Name == nameof(DerivedTagHelper.NewProperty) && + p.PropertyType.Name == typeof(Type).Name), + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.Name, actualAttribute.Name); + Assert.Equal( + expectedAttribute.DictionaryAttributePrefix, + actualAttribute.DictionaryAttributePrefix); + }); + + var expectedVirtualProperty = expectedProperties.First( + p => p.Name == nameof(DerivedTagHelper.VirtualProperty)); + var actualVirtualProperty = actual.Properties.First( + p => p.Name == nameof(DerivedTagHelper.VirtualProperty)); + + Assert.Empty(expectedVirtualProperty.GetCustomAttributes()); + Assert.Empty(actualVirtualProperty.GetCustomAttributes()); + } + + [Fact] + public void GetExportedTypes_CorrectlyIdentifiesIfTypeDerivesFromDictionary() + { + // Arrange + var compilation = CompilationUtility.GetCompilation(nameof(TypeWithDictionaryProperties)); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var exportedType = Assert.Single(exportedTypes); + AssertEqual(typeof(TypeWithDictionaryProperties), exportedType); + } + + [Fact] + public void GetExportedTypes_WorksCorrectlyForOverridenProperties() + { + // Arrange + var compilation = CompilationUtility.GetCompilation("TypesWithInheritedProperties"); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + Assert.Collection(exportedTypes, + typeInfo => + { + AssertEqual(typeof(Animal), typeInfo); + }, + typeInfo => + { + AssertEqual(typeof(Mammal), typeInfo); + }, + typeInfo => + { + AssertEqual(typeof(Dog), typeInfo); + }); + } + + [Fact] + public void GetExportedTypes_WorksIfAttributeConstructorArgumentsAreOutOfOrder() + { + // Arrange + var expectedType = typeof(TypeWithNamedAttributes); + var compilation = CompilationUtility.GetCompilation(expectedType.Name); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var exportedType = Assert.Single(exportedTypes); + AssertEqual(expectedType, exportedType); + + var attributes = exportedType.GetCustomAttributes(); + + AssertAttributes( + expectedType.GetTypeInfo(), + exportedType, + (expectedAttribute, actualAttribute) => + { + Assert.Equal(expectedAttribute.FirstArgument, actualAttribute.FirstArgument); + Assert.Equal(expectedAttribute.SecondArgument, actualAttribute.SecondArgument); + Assert.Equal(expectedAttribute.ThirdArgument, actualAttribute.ThirdArgument); + }); + } + + [Fact] + public void GetExportedTypes_WorksIfAttributesAreMalformedErrors() + { + // Arrange + var compilation = CompilationUtility.GetCompilation("BadFiles.TypeWithMalformedAttribute"); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var actual = Assert.Single(exportedTypes); + var targetElementAttribute = Assert.Single(actual.GetCustomAttributes()); + Assert.Equal("img", targetElementAttribute.Tag); + Assert.Null(targetElementAttribute.Attributes); + + Assert.Empty(actual.GetCustomAttributes()); + var ex = Assert.Throws( + () => actual.GetCustomAttributes()); + Assert.Equal($"Unable to find a suitable constructor for type '{typeof(CustomValidationAttribute).FullName}'.", + ex.Message); + + ex = Assert.Throws( + () => actual.GetCustomAttributes()); + Assert.Equal($"Unable to find a suitable constructor for type '{typeof(RestrictChildrenAttribute).FullName}'.", + ex.Message); + } + + [Fact] + public void GetExportedTypes_WorksForPropertiesWithErrors() + { + // Arrange + var compilation = CompilationUtility.GetCompilation("BadFiles.TypeWithMalformedProperties"); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var actual = Assert.Single(exportedTypes); + Assert.Collection(actual.Properties, + property => + { + Assert.Equal("DateTime", property.Name); + Assert.Equal(typeof(DateTime).Name, property.PropertyType.Name); + Assert.True(property.HasPublicGetter); + Assert.False(property.HasPublicSetter); + }, + property => + { + Assert.Equal("DateTime2", property.Name); + Assert.Equal(typeof(int).Name, property.PropertyType.Name); + Assert.True(property.HasPublicGetter); + Assert.False(property.HasPublicSetter); + }, + property => + { + Assert.Equal("CustomOrder", property.Name); + Assert.Equal(typeof(string).Name, property.PropertyType.Name); + Assert.True(property.HasPublicGetter); + Assert.True(property.HasPublicSetter); + }); + } + + [Fact] + public void GetExportedTypes_WorksForTypesWithErrors() + { + // Arrange + var compilation = CompilationUtility.GetCompilation("BadFiles.TypeWithMissingReferences"); + var tagHelperResolver = new PrecompilationTagHelperTypeResolver(compilation); + + // Act + var exportedTypes = tagHelperResolver.GetExportedTypes(compilation.AssemblyName); + + // Assert + var type = Assert.Single(exportedTypes); + Assert.False(type.IsTagHelper); + } + + private static void AssertAttributes( + MemberInfo expected, + IMemberInfo actual, + Action assertItem) + where TAttribute : Attribute + { + Assert.Equal( + expected.GetCustomAttributes(), + actual.GetCustomAttributes(), + new DelegateAssertion(assertItem)); + } + + private static void AssertEqual(Type expected, ITypeInfo actual) + { + AssertEqual(new RuntimeTypeInfo(expected.GetTypeInfo()), actual, assertProperties: true); + } + + private static void AssertEqual(ITypeInfo expected, ITypeInfo actual, bool assertProperties) + { + var runtimeType = Assert.IsType(expected); + + var actualFullName = actual.FullName.Replace( + CompilationUtility.GeneratedAssemblyName, + runtimeType.TypeInfo.Assembly.GetName().Name); + + Assert.Equal(expected.Name, actual.Name); + if (expected.FullName != actualFullName) + { + Console.WriteLine("!!!"); + Console.WriteLine(runtimeType.TypeInfo.FullName); + Console.WriteLine(actualFullName); + } + + Assert.Equal(expected.FullName, actualFullName); + Assert.Equal(expected.IsPublic, actual.IsPublic); + Assert.Equal(expected.IsAbstract, actual.IsAbstract); + Assert.Equal(expected.IsGenericType, actual.IsGenericType); + Assert.Equal(expected.IsTagHelper, actual.IsTagHelper); + Assert.Equal( + expected.GetGenericDictionaryParameters(), + actual.GetGenericDictionaryParameters(), + new DelegateAssertion((x, y) => AssertEqual(x, y, assertProperties: false))); + + if (assertProperties) + { + Assert.Equal( + expected.Properties.OrderBy(p => p.Name), + actual.Properties.OrderBy(p => p.Name), + new DelegateAssertion((x, y) => AssertEqual(x, y))); + } + } + + private static void AssertEqual(IPropertyInfo expected, IPropertyInfo actual) + { + Assert.Equal(expected.Name, actual.Name); + Assert.Equal(expected.HasPublicGetter, actual.HasPublicGetter); + Assert.Equal(expected.HasPublicSetter, actual.HasPublicSetter); + AssertEqual(expected.PropertyType, actual.PropertyType, assertProperties: false); + } + + private class DelegateAssertion : IEqualityComparer + { + private readonly Action _assert; + + public DelegateAssertion(Action assert) + { + _assert = assert; + } + + public bool Equals(T x, T y) + { + _assert(x, y); + return true; + } + + public int GetHashCode(T obj) => 0; + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/project.json b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/project.json new file mode 100644 index 0000000000..9cdb92972f --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Precompilation.Test/project.json @@ -0,0 +1,33 @@ +{ + "version": "1.0.0", + "dependencies": { + "Microsoft.AspNet.Razor.Runtime.Precompilation.Files": "1.0.0-*", + "Microsoft.AspNet.Razor.Runtime.Test": "1.0.0-*", + "Microsoft.AspNet.Razor.Test.Sources": { + "version": "4.0.0-*", + "type": "build" + }, + "Microsoft.Dnx.Compilation.CSharp.Common": "1.0.0-*", + "Microsoft.Framework.HashCodeCombiner.Sources": { + "type": "build", + "version": "1.0.0-*" + } + }, + "resource": [ + "../Microsoft.AspNet.Razor.Runtime.Precompilation.Files/*.cs", + "../Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/*.cs", + "BadFiles/*.cs" + ], + "commands": { + "test": "xunit.runner.aspnet" + }, + "frameworks": { + "dnx451": { + "dependencies": { + "Moq": "4.2.1312.1622" + } + }, + "dnxcore50": { + } + } +} diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTagHelperDescriptorFactoryTest.cs new file mode 100644 index 0000000000..3515afe2bd --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTagHelperDescriptorFactoryTest.cs @@ -0,0 +1,123 @@ +// 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.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); + + // Act + var descriptors = TagHelperDescriptorFactory.CreateDescriptors( + objectAssemblyName, + GetTypeInfo(typeof(object)), + designTime: false, + errorSink: errorSink); + + // Assert + Assert.Empty(errorSink.Errors); + var descriptor = Assert.Single(descriptors); + Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); + } + + // TagHelperDesignTimeDescriptors are not created in CoreCLR. +#if !DNXCORE50 + public static TheoryData OutputElementHintData + { + get + { + // tagHelperType, expectedDescriptors + return new TheoryData + { + { + 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(); + + // Act + var descriptors = TagHelperDescriptorFactory.CreateDescriptors( + AssemblyName, + GetTypeInfo(tagHelperType), + designTime: true, + 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 + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTypeInfoTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTypeInfoTest.cs index 8c4a3290e0..da101d6cd5 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTypeInfoTest.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/RuntimeTypeInfoTest.cs @@ -5,39 +5,64 @@ using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using System.Linq; using System.Reflection; using System.Threading.Tasks; -using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Xunit; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { public class RuntimeTypeInfoTest { + private static readonly string StringFullName = typeof(string).FullName; + private static readonly string CollectionsNamespace = typeof(IDictionary<,>).Namespace; + + public static TheoryData RuntimeTypeInfo_ReturnsMetadataOfAdaptingTypeData => + new TheoryData + { + { typeof(int), typeof(int).FullName }, + { typeof(string), typeof(string).FullName }, + { typeof(Tuple<>), typeof(Tuple<>).FullName }, + { typeof(Tuple<,>), typeof(Tuple<,>).FullName }, + { + typeof(IDictionary), + $"{typeof(IDictionary<,>).FullName}[[{StringFullName}],[{StringFullName}]]" + }, + { + typeof(IDictionary>), + $"{typeof(IDictionary<,>).FullName}[[{StringFullName}],[{typeof(IDictionary<,>).FullName}" + + $"[[{StringFullName}],[{typeof(CustomType).FullName}]]]]" + }, + { + typeof(IList, Tuple>>>), + $"{typeof(IList<>).FullName}[[{typeof(IReadOnlyList<>).FullName}[[{typeof(IDictionary<,>).FullName}[[" + + $"{typeof(List<>).FullName}[[{StringFullName}]]],[{typeof(Tuple<,>).FullName}[[{typeof(CustomType).FullName}]," + + $"[{typeof(object).FullName}[]]]]]]]]]" + }, + { typeof(AbstractType), typeof(AbstractType).FullName }, + { typeof(PrivateType), typeof(PrivateType).FullName }, + { typeof(KnownKeyDictionary<>), typeof(KnownKeyDictionary<>).FullName }, + { + typeof(KnownKeyDictionary), + $"{typeof(KnownKeyDictionary<>).Namespace}" + + $".RuntimeTypeInfoTest+KnownKeyDictionary`1[[{StringFullName}]]" + } + }; + [Theory] - [InlineData(typeof(int))] - [InlineData(typeof(string))] - [InlineData(typeof(Tuple<,>))] - [InlineData(typeof(IDictionary))] - [InlineData(typeof(IDictionary>))] - [InlineData(typeof(AbstractType))] - [InlineData(typeof(PrivateType))] - [InlineData(typeof(KnownKeyDictionary<>))] - [InlineData(typeof(KnownKeyDictionary))] - public void RuntimeTypeInfo_ReturnsMetadataOfAdaptingType(Type type) + [MemberData(nameof(RuntimeTypeInfo_ReturnsMetadataOfAdaptingTypeData))] + public void RuntimeTypeInfo_ReturnsMetadataOfAdaptingType(Type type, string expectedFullName) { // Arrange var typeInfo = type.GetTypeInfo(); var runtimeTypeInfo = new RuntimeTypeInfo(typeInfo); // Act and Assert - Assert.Same(runtimeTypeInfo.TypeInfo, typeInfo); - Assert.Equal(runtimeTypeInfo.Name, typeInfo.Name); - Assert.Equal(runtimeTypeInfo.FullName, typeInfo.FullName); - Assert.Equal(runtimeTypeInfo.IsAbstract, typeInfo.IsAbstract); - Assert.Equal(runtimeTypeInfo.IsGenericType, typeInfo.IsGenericType); - Assert.Equal(runtimeTypeInfo.IsPublic, typeInfo.IsPublic); + Assert.Same(typeInfo, runtimeTypeInfo.TypeInfo); + Assert.Equal(typeInfo.Name, runtimeTypeInfo.Name); + Assert.Equal(expectedFullName, runtimeTypeInfo.FullName); + Assert.Equal(typeInfo.IsAbstract, runtimeTypeInfo.IsAbstract); + Assert.Equal(typeInfo.IsGenericType, runtimeTypeInfo.IsGenericType); + Assert.Equal(typeInfo.IsPublic, runtimeTypeInfo.IsPublic); } [Fact] @@ -150,23 +175,62 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers [InlineData(typeof(ImplementsIDictionary), new[] { typeof(List), typeof(string) })] [InlineData(typeof(IDictionary>), new[] { typeof(string), typeof(IDictionary) })] - [InlineData(typeof(Dictionary<,>), new Type[] { null, null })] - [InlineData(typeof(KnownKeyDictionary<>), new[] { typeof(string), null })] [InlineData(typeof(KnownKeyDictionary), new[] { typeof(string), typeof(ImplementsIDictionary) })] - public void GetGenericDictionaryParameterNames_ReturnsKeyAndValueParameterTypeNames( + public void GetGenericDictionaryParameters_ReturnsKeyAndValueParameterTypeNames( Type type, Type[] expectedTypes) { // Arrange var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo()); - var expected = expectedTypes.Select(t => t?.FullName); // Act - var actual = runtimeTypeInfo.GetGenericDictionaryParameterNames(); + var actual = runtimeTypeInfo.GetGenericDictionaryParameters(); // Assert - Assert.Equal(expected, actual); + 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] @@ -181,12 +245,104 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers var runtimeTypeInfo = new RuntimeTypeInfo(type.GetTypeInfo()); // Act - var actual = runtimeTypeInfo.GetGenericDictionaryParameterNames(); + 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 + { + { 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), + RuntimeTypeInfo.SanitizeFullName(typeof(IDictionary).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))] + [InlineData(typeof(KnownKeyDictionary), typeof(IDictionary))] + [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 { } @@ -371,5 +527,68 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers 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 Properties + { + get + { + throw new NotImplementedException(); + } + } + + public IEnumerable GetCustomAttributes() where TAttribute : Attribute + { + throw new NotImplementedException(); + } + + public ITypeInfo[] GetGenericDictionaryParameters() + { + throw new NotImplementedException(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs index 95009711be..0444abfe7d 100644 --- a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TagHelperDescriptorFactoryTest.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using System.Reflection; using Microsoft.AspNet.Razor.TagHelpers; @@ -12,10 +11,12 @@ using Xunit; namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { - public class TagHelperDescriptorFactoryTest + public abstract class TagHelperDescriptorFactoryTest { - private static readonly string AssemblyName = - typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName().Name; + protected static readonly AssemblyName TagHelperDescriptorFactoryTestAssembly = + typeof(TagHelperDescriptorFactoryTest).GetTypeInfo().Assembly.GetName(); + + protected static readonly string AssemblyName = TagHelperDescriptorFactoryTestAssembly.Name; public static TheoryData RestrictChildrenData { @@ -74,6 +75,8 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers } } + public abstract ITypeInfo GetTypeInfo(Type tagHelperType); + [Theory] [MemberData(nameof(RestrictChildrenData))] public void CreateDescriptors_CreatesDescriptorsWithAllowedChildren( @@ -189,88 +192,6 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); } - // TagHelperDesignTimeDescriptors are not created in CoreCLR. -#if !DNXCORE50 - public static TheoryData OutputElementHintData - { - get - { - // tagHelperType, expectedDescriptors - return new TheoryData - { - { - 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(); - - // Act - var descriptors = TagHelperDescriptorFactory.CreateDescriptors( - AssemblyName, - GetTypeInfo(tagHelperType), - designTime: true, - 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 - public static TheoryData EditorBrowsableData { get @@ -852,34 +773,11 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers GetTypeInfo(typeof(InheritedNotOverriddenAttributeTagHelper)), designTime: false, errorSink: errorSink); - // Assert Assert.Empty(errorSink.Errors); Assert.Equal(expectedDescriptors, descriptors, CaseSensitiveTagHelperDescriptorComparer.Default); } - [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); - - // Act - var descriptors = TagHelperDescriptorFactory.CreateDescriptors( - objectAssemblyName, - GetTypeInfo(typeof(object)), - designTime: false, - errorSink: errorSink); - - // Assert - Assert.Empty(errorSink.Errors); - var descriptor = Assert.Single(descriptors); - Assert.Equal(expectedDescriptor, descriptor, CaseSensitiveTagHelperDescriptorComparer.Default); - } - [Fact] public void CreateDescriptors_BuildsDescriptorsWithInheritedProperties() { @@ -1411,7 +1309,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { Name = "dictionary-property", PropertyName = nameof(DefaultValidHtmlAttributePrefix.DictionaryProperty), - TypeName = typeof(IDictionary).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(IDictionary).FullName) }, new TagHelperAttributeDescriptor { @@ -1431,7 +1329,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { Name = "valid-name", PropertyName = nameof(SingleValidHtmlAttributePrefix.DictionaryProperty), - TypeName = typeof(IDictionary).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(IDictionary).FullName) }, new TagHelperAttributeDescriptor { @@ -1451,31 +1349,31 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers { Name = "valid-name1", PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryProperty), - TypeName = typeof(Dictionary).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(Dictionary).FullName) }, new TagHelperAttributeDescriptor { Name = "valid-name2", PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionarySubclassProperty), - TypeName = typeof(DictionarySubclass).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(DictionarySubclass).FullName) }, new TagHelperAttributeDescriptor { Name = "valid-name3", PropertyName = nameof(MultipleValidHtmlAttributePrefix.DictionaryWithoutParameterlessConstructorProperty), - TypeName = typeof(DictionaryWithoutParameterlessConstructor).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(DictionaryWithoutParameterlessConstructor).FullName) }, new TagHelperAttributeDescriptor { Name = "valid-name4", PropertyName = nameof(MultipleValidHtmlAttributePrefix.GenericDictionarySubclassProperty), - TypeName = typeof(GenericDictionarySubclass).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(GenericDictionarySubclass).FullName) }, new TagHelperAttributeDescriptor { Name = "valid-name5", PropertyName = nameof(MultipleValidHtmlAttributePrefix.SortedDictionaryProperty), - TypeName = typeof(SortedDictionary).FullName + TypeName = RuntimeTypeInfo.SanitizeFullName(typeof(SortedDictionary).FullName) }, new TagHelperAttributeDescriptor { @@ -2072,7 +1970,7 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers return data; } - private static TagHelperDescriptor CreateTagHelperDescriptor( + protected static TagHelperDescriptor CreateTagHelperDescriptor( string tagName, string typeName, string assemblyName, @@ -2100,392 +1998,5 @@ namespace Microsoft.AspNet.Razor.Runtime.TagHelpers TypeName = propertyInfo.PropertyType.FullName }; } - - private static ITypeInfo GetTypeInfo(Type tagHelperType) => - new RuntimeTypeInfo(tagHelperType.GetTypeInfo()); - - [RestrictChildren("p")] - private class RestrictChildrenTagHelper - { - } - - [RestrictChildren("p", "strong")] - private class DoubleRestrictChildrenTagHelper - { - } - - [TargetElement("p")] - [TargetElement("div")] - [RestrictChildren("p", "strong")] - private class MultiTargetRestrictChildrenTagHelper - { - } - - [TargetElement("input", TagStructure = TagStructure.WithoutEndTag)] - private class TagStructureTagHelper : TagHelper - { - } - - [TargetElement("p", TagStructure = TagStructure.NormalOrSelfClosing)] - [TargetElement("input", TagStructure = TagStructure.WithoutEndTag)] - private class MultiSpecifiedTagStructureTagHelper : TagHelper - { - } - - [TargetElement("p")] - [TargetElement("input", TagStructure = TagStructure.WithoutEndTag)] - private class MultiWithUnspecifiedTagStructureTagHelper : TagHelper - { - } - - [EditorBrowsable(EditorBrowsableState.Always)] - private class DefaultEditorBrowsableTagHelper : TagHelper - { - [EditorBrowsable(EditorBrowsableState.Always)] - public int Property { get; set; } - } - - private class HiddenPropertyEditorBrowsableTagHelper : TagHelper - { - [EditorBrowsable(EditorBrowsableState.Never)] - public int Property { get; set; } - } - - private class MultiPropertyEditorBrowsableTagHelper : TagHelper - { - [EditorBrowsable(EditorBrowsableState.Never)] - public int Property { get; set; } - - public virtual int Property2 { get; set; } - } - - private class OverriddenPropertyEditorBrowsableTagHelper : MultiPropertyEditorBrowsableTagHelper - { - [EditorBrowsable(EditorBrowsableState.Never)] - public override int Property2 { get; set; } - } - - [EditorBrowsable(EditorBrowsableState.Never)] - private class EditorBrowsableTagHelper : TagHelper - { - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual int Property { get; set; } - } - - private class InheritedEditorBrowsableTagHelper : EditorBrowsableTagHelper - { - public override int Property { get; set; } - } - - [EditorBrowsable(EditorBrowsableState.Advanced)] - private class OverriddenEditorBrowsableTagHelper : EditorBrowsableTagHelper - { - [EditorBrowsable(EditorBrowsableState.Advanced)] - public override int Property { get; set; } - } - - [TargetElement("p")] - [TargetElement("div")] - [EditorBrowsable(EditorBrowsableState.Never)] - private class MultiEditorBrowsableTagHelper : TagHelper - { - } - - [TargetElement(Attributes = "class*")] - private class AttributeWildcardTargetingTagHelper : TagHelper - { - } - - [TargetElement(Attributes = "class*,style*")] - private class MultiAttributeWildcardTargetingTagHelper : TagHelper - { - } - - [TargetElement(Attributes = "class")] - private class AttributeTargetingTagHelper : TagHelper - { - } - - [TargetElement(Attributes = "class,style")] - private class MultiAttributeTargetingTagHelper : TagHelper - { - } - - [TargetElement(Attributes = "custom")] - [TargetElement(Attributes = "class,style")] - private class MultiAttributeAttributeTargetingTagHelper : TagHelper - { - } - - [TargetElement(Attributes = "style")] - private class InheritedAttributeTargetingTagHelper : AttributeTargetingTagHelper - { - } - - [TargetElement("input", Attributes = "class")] - private class RequiredAttributeTagHelper : TagHelper - { - } - - [TargetElement("div", Attributes = "class")] - private class InheritedRequiredAttributeTagHelper : RequiredAttributeTagHelper - { - } - - [TargetElement("div", Attributes = "class")] - [TargetElement("input", Attributes = "class")] - private class MultiAttributeRequiredAttributeTagHelper : TagHelper - { - } - - [TargetElement("input", Attributes = "style")] - [TargetElement("input", Attributes = "class")] - private class MultiAttributeSameTagRequiredAttributeTagHelper : TagHelper - { - } - - [TargetElement("input", Attributes = "class,style")] - private class MultiRequiredAttributeTagHelper : TagHelper - { - } - - [TargetElement("div", Attributes = "style")] - private class InheritedMultiRequiredAttributeTagHelper : MultiRequiredAttributeTagHelper - { - } - - [TargetElement("div", Attributes = "class,style")] - [TargetElement("input", Attributes = "class,style")] - private class MultiTagMultiRequiredAttributeTagHelper : TagHelper - { - } - - [TargetElement("p")] - [TargetElement("div")] - private class MultiTagTagHelper - { - public string ValidAttribute { get; set; } - } - - private class InheritedMultiTagTagHelper : MultiTagTagHelper - { - } - - [TargetElement("p")] - [TargetElement("p")] - [TargetElement("div")] - [TargetElement("div")] - private class DuplicateTagNameTagHelper - { - } - - [TargetElement("data-condition")] - private class OverrideNameTagHelper - { - } - - private class InheritedSingleAttributeTagHelper : SingleAttributeTagHelper - { - } - - private class DuplicateAttributeNameTagHelper - { - public string MyNameIsLegion { get; set; } - - [HtmlAttributeName("my-name-is-legion")] - public string Fred { get; set; } - } - - private class NotBoundAttributeTagHelper - { - public object BoundProperty { get; set; } - - [HtmlAttributeNotBound] - public string NotBoundProperty { get; set; } - - [HtmlAttributeName("unused")] - [HtmlAttributeNotBound] - public string NamedNotBoundProperty { get; set; } - } - - private class OverriddenAttributeTagHelper - { - [HtmlAttributeName("SomethingElse")] - public virtual string ValidAttribute1 { get; set; } - - [HtmlAttributeName("Something-Else")] - public string ValidAttribute2 { get; set; } - } - - private class InheritedOverriddenAttributeTagHelper : OverriddenAttributeTagHelper - { - public override string ValidAttribute1 { get; set; } - } - - private class InheritedNotOverriddenAttributeTagHelper : OverriddenAttributeTagHelper - { - } - - private class ALLCAPSTAGHELPER : TagHelper - { - public int ALLCAPSATTRIBUTE { get; set; } - } - - private class CAPSOnOUTSIDETagHelper : TagHelper - { - public int CAPSOnOUTSIDEATTRIBUTE { get; set; } - } - - private class capsONInsideTagHelper : TagHelper - { - public int capsONInsideattribute { get; set; } - } - - private class One1Two2Three3TagHelper : TagHelper - { - public int One1Two2Three3Attribute { get; set; } - } - - private class ONE1TWO2THREE3TagHelper : TagHelper - { - public int ONE1TWO2THREE3Attribute { get; set; } - } - - private class First_Second_ThirdHiTagHelper : TagHelper - { - public int First_Second_ThirdAttribute { get; set; } - } - - private class UNSuffixedCLASS : TagHelper - { - public int UNSuffixedATTRIBUTE { get; set; } - } - - private class InvalidBoundAttribute : TagHelper - { - public string DataSomething { get; set; } - } - - private class InvalidBoundAttributeWithValid : SingleAttributeTagHelper - { - public string DataSomething { get; set; } - } - - private class OverriddenInvalidBoundAttributeWithValid : TagHelper - { - [HtmlAttributeName("valid-something")] - public string DataSomething { get; set; } - } - - private class OverriddenValidBoundAttributeWithInvalid : TagHelper - { - [HtmlAttributeName("data-something")] - public string ValidSomething { get; set; } - } - - private class OverriddenValidBoundAttributeWithInvalidUpperCase : TagHelper - { - [HtmlAttributeName("DATA-SOMETHING")] - public string ValidSomething { get; set; } - } - - private class DefaultValidHtmlAttributePrefix : TagHelper - { - public IDictionary DictionaryProperty { get; set; } - } - - private class SingleValidHtmlAttributePrefix : TagHelper - { - [HtmlAttributeName("valid-name")] - public IDictionary DictionaryProperty { get; set; } - } - - private class MultipleValidHtmlAttributePrefix : TagHelper - { - [HtmlAttributeName("valid-name1", DictionaryAttributePrefix = "valid-prefix1-")] - public Dictionary DictionaryProperty { get; set; } - - [HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")] - public DictionarySubclass DictionarySubclassProperty { get; set; } - - [HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")] - public DictionaryWithoutParameterlessConstructor DictionaryWithoutParameterlessConstructorProperty { get; set; } - - [HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")] - public GenericDictionarySubclass GenericDictionarySubclassProperty { get; set; } - - [HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")] - public SortedDictionary SortedDictionaryProperty { get; set; } - - [HtmlAttributeName("valid-name6")] - public string StringProperty { get; set; } - - public IDictionary GetOnlyDictionaryProperty { get; } - - [HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")] - public IDictionary GetOnlyDictionaryPropertyWithAttributePrefix { get; } - } - - private class SingleInvalidHtmlAttributePrefix : TagHelper - { - [HtmlAttributeName("valid-name", DictionaryAttributePrefix = "valid-prefix")] - public string StringProperty { get; set; } - } - - private class MultipleInvalidHtmlAttributePrefix : TagHelper - { - [HtmlAttributeName("valid-name1")] - public long LongProperty { get; set; } - - [HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")] - public Dictionary DictionaryOfIntProperty { get; set; } - - [HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")] - public IReadOnlyDictionary ReadOnlyDictionaryProperty { get; set; } - - [HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")] - public int IntProperty { get; set; } - - [HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")] - public DictionaryOfIntSubclass DictionaryOfIntSubclassProperty { get; set; } - - [HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")] - public IDictionary GetOnlyDictionaryAttributePrefix { get; } - - [HtmlAttributeName("invalid-name7")] - public IDictionary GetOnlyDictionaryPropertyWithAttributeName { get; } - } - - private class DictionarySubclass : Dictionary - { - } - - private class DictionaryWithoutParameterlessConstructor : Dictionary - { - public DictionaryWithoutParameterlessConstructor(int count) - : base() - { - } - } - - private class DictionaryOfIntSubclass : Dictionary - { - } - - private class GenericDictionarySubclass : Dictionary - { - } - - [OutputElementHint("strong")] - private class OutputElementHintTagHelper : TagHelper - { - } - - [TargetElement("a")] - [TargetElement("p")] - [OutputElementHint("div")] - private class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper - { - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CommonTagHelpers.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/CommonTagHelpers.cs similarity index 100% rename from test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/CommonTagHelpers.cs rename to test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/CommonTagHelpers.cs diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/DoesNotImplementRealITagHelper.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/DoesNotImplementRealITagHelper.cs similarity index 100% rename from test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/DoesNotImplementRealITagHelper.cs rename to test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/DoesNotImplementRealITagHelper.cs diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/IFakeTagHelper.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/IFakeTagHelper.cs similarity index 100% rename from test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/IFakeTagHelper.cs rename to test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/IFakeTagHelper.cs diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/ImplementsRealTagHelper.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/ImplementsRealTagHelper.cs similarity index 100% rename from test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/ImplementsRealTagHelper.cs rename to test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/ImplementsRealTagHelper.cs diff --git a/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs new file mode 100644 index 0000000000..64f185848c --- /dev/null +++ b/test/Microsoft.AspNet.Razor.Runtime.Test/TagHelpers/TestTagHelpers/TagHelperDescriptorFactoryTagHelpers.cs @@ -0,0 +1,446 @@ +// 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.ComponentModel; +using System.Threading.Tasks; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; + +namespace Microsoft.AspNet.Razor.Runtime.TagHelpers +{ + + [RestrictChildren("p")] + public class RestrictChildrenTagHelper + { + } + + [RestrictChildren("p", "strong")] + public class DoubleRestrictChildrenTagHelper + { + } + + [TargetElement("p")] + [TargetElement("div")] + [RestrictChildren("p", "strong")] + public class MultiTargetRestrictChildrenTagHelper + { + } + + [TargetElement("input", TagStructure = TagStructure.WithoutEndTag)] + public class TagStructureTagHelper : TagHelper + { + } + + [TargetElement("p", TagStructure = TagStructure.NormalOrSelfClosing)] + [TargetElement("input", TagStructure = TagStructure.WithoutEndTag)] + public class MultiSpecifiedTagStructureTagHelper : TagHelper + { + } + + [TargetElement("p")] + [TargetElement("input", TagStructure = TagStructure.WithoutEndTag)] + public class MultiWithUnspecifiedTagStructureTagHelper : TagHelper + { + } + + [EditorBrowsable(EditorBrowsableState.Always)] + public class DefaultEditorBrowsableTagHelper : TagHelper + { + [EditorBrowsable(EditorBrowsableState.Always)] + public int Property { get; set; } + } + + public class HiddenPropertyEditorBrowsableTagHelper : TagHelper + { + [EditorBrowsable(EditorBrowsableState.Never)] + public int Property { get; set; } + } + + public class MultiPropertyEditorBrowsableTagHelper : TagHelper + { + [EditorBrowsable(EditorBrowsableState.Never)] + public int Property { get; set; } + + public virtual int Property2 { get; set; } + } + + public class OverriddenPropertyEditorBrowsableTagHelper : MultiPropertyEditorBrowsableTagHelper + { + [EditorBrowsable(EditorBrowsableState.Never)] + public override int Property2 { get; set; } + } + + [EditorBrowsable(EditorBrowsableState.Never)] + public class EditorBrowsableTagHelper : TagHelper + { + [EditorBrowsable(EditorBrowsableState.Never)] + public virtual int Property { get; set; } + } + + public class InheritedEditorBrowsableTagHelper : EditorBrowsableTagHelper + { + public override int Property { get; set; } + } + + [EditorBrowsable(EditorBrowsableState.Advanced)] + public class OverriddenEditorBrowsableTagHelper : EditorBrowsableTagHelper + { + [EditorBrowsable(EditorBrowsableState.Advanced)] + public override int Property { get; set; } + } + + [TargetElement("p")] + [TargetElement("div")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class MultiEditorBrowsableTagHelper : TagHelper + { + } + + [TargetElement(Attributes = "class*")] + public class AttributeWildcardTargetingTagHelper : TagHelper + { + } + + [TargetElement(Attributes = "class*,style*")] + public class MultiAttributeWildcardTargetingTagHelper : TagHelper + { + } + + [TargetElement(Attributes = "class")] + public class AttributeTargetingTagHelper : TagHelper + { + } + + [TargetElement(Attributes = "class,style")] + public class MultiAttributeTargetingTagHelper : TagHelper + { + } + + [TargetElement(Attributes = "custom")] + [TargetElement(Attributes = "class,style")] + public class MultiAttributeAttributeTargetingTagHelper : TagHelper + { + } + + [TargetElement(Attributes = "style")] + public class InheritedAttributeTargetingTagHelper : AttributeTargetingTagHelper + { + } + + [TargetElement("input", Attributes = "class")] + public class RequiredAttributeTagHelper : TagHelper + { + } + + [TargetElement("div", Attributes = "class")] + public class InheritedRequiredAttributeTagHelper : RequiredAttributeTagHelper + { + } + + [TargetElement("div", Attributes = "class")] + [TargetElement("input", Attributes = "class")] + public class MultiAttributeRequiredAttributeTagHelper : TagHelper + { + } + + [TargetElement("input", Attributes = "style")] + [TargetElement("input", Attributes = "class")] + public class MultiAttributeSameTagRequiredAttributeTagHelper : TagHelper + { + } + + [TargetElement("input", Attributes = "class,style")] + public class MultiRequiredAttributeTagHelper : TagHelper + { + } + + [TargetElement("div", Attributes = "style")] + public class InheritedMultiRequiredAttributeTagHelper : MultiRequiredAttributeTagHelper + { + } + + [TargetElement("div", Attributes = "class,style")] + [TargetElement("input", Attributes = "class,style")] + public class MultiTagMultiRequiredAttributeTagHelper : TagHelper + { + } + + [TargetElement("p")] + [TargetElement("div")] + public class MultiTagTagHelper + { + public string ValidAttribute { get; set; } + } + + public class InheritedMultiTagTagHelper : MultiTagTagHelper + { + } + + [TargetElement("p")] + [TargetElement("p")] + [TargetElement("div")] + [TargetElement("div")] + public class DuplicateTagNameTagHelper + { + } + + [TargetElement("data-condition")] + public class OverrideNameTagHelper + { + } + + public class InheritedSingleAttributeTagHelper : SingleAttributeTagHelper + { + } + + public class DuplicateAttributeNameTagHelper + { + public string MyNameIsLegion { get; set; } + + [HtmlAttributeName("my-name-is-legion")] + public string Fred { get; set; } + } + + public class NotBoundAttributeTagHelper + { + public object BoundProperty { get; set; } + + [HtmlAttributeNotBound] + public string NotBoundProperty { get; set; } + + [HtmlAttributeName("unused")] + [HtmlAttributeNotBound] + public string NamedNotBoundProperty { get; set; } + } + + public class OverriddenAttributeTagHelper + { + [HtmlAttributeName("SomethingElse")] + public virtual string ValidAttribute1 { get; set; } + + [HtmlAttributeName("Something-Else")] + public string ValidAttribute2 { get; set; } + } + + public class InheritedOverriddenAttributeTagHelper : OverriddenAttributeTagHelper + { + public override string ValidAttribute1 { get; set; } + } + + public class InheritedNotOverriddenAttributeTagHelper : OverriddenAttributeTagHelper + { + } + + public class ALLCAPSTAGHELPER : TagHelper + { + public int ALLCAPSATTRIBUTE { get; set; } + } + + public class CAPSOnOUTSIDETagHelper : TagHelper + { + public int CAPSOnOUTSIDEATTRIBUTE { get; set; } + } + + public class capsONInsideTagHelper : TagHelper + { + public int capsONInsideattribute { get; set; } + } + + public class One1Two2Three3TagHelper : TagHelper + { + public int One1Two2Three3Attribute { get; set; } + } + + public class ONE1TWO2THREE3TagHelper : TagHelper + { + public int ONE1TWO2THREE3Attribute { get; set; } + } + + public class First_Second_ThirdHiTagHelper : TagHelper + { + public int First_Second_ThirdAttribute { get; set; } + } + + public class UNSuffixedCLASS : TagHelper + { + public int UNSuffixedATTRIBUTE { get; set; } + } + + public class InvalidBoundAttribute : TagHelper + { + public string DataSomething { get; set; } + } + + public class InvalidBoundAttributeWithValid : SingleAttributeTagHelper + { + public string DataSomething { get; set; } + } + + public class OverriddenInvalidBoundAttributeWithValid : TagHelper + { + [HtmlAttributeName("valid-something")] + public string DataSomething { get; set; } + } + + public class OverriddenValidBoundAttributeWithInvalid : TagHelper + { + [HtmlAttributeName("data-something")] + public string ValidSomething { get; set; } + } + + public class OverriddenValidBoundAttributeWithInvalidUpperCase : TagHelper + { + [HtmlAttributeName("DATA-SOMETHING")] + public string ValidSomething { get; set; } + } + + public class DefaultValidHtmlAttributePrefix : TagHelper + { + public IDictionary DictionaryProperty { get; set; } + } + + public class SingleValidHtmlAttributePrefix : TagHelper + { + [HtmlAttributeName("valid-name")] + public IDictionary DictionaryProperty { get; set; } + } + + public class MultipleValidHtmlAttributePrefix : TagHelper + { + [HtmlAttributeName("valid-name1", DictionaryAttributePrefix = "valid-prefix1-")] + public Dictionary DictionaryProperty { get; set; } + + [HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")] + public DictionarySubclass DictionarySubclassProperty { get; set; } + + [HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")] + public DictionaryWithoutParameterlessConstructor DictionaryWithoutParameterlessConstructorProperty { get; set; } + + [HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")] + public GenericDictionarySubclass GenericDictionarySubclassProperty { get; set; } + + [HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")] + public SortedDictionary SortedDictionaryProperty { get; set; } + + [HtmlAttributeName("valid-name6")] + public string StringProperty { get; set; } + + public IDictionary GetOnlyDictionaryProperty { get; } + + [HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")] + public IDictionary GetOnlyDictionaryPropertyWithAttributePrefix { get; } + } + + public class SingleInvalidHtmlAttributePrefix : TagHelper + { + [HtmlAttributeName("valid-name", DictionaryAttributePrefix = "valid-prefix")] + public string StringProperty { get; set; } + } + + public class MultipleInvalidHtmlAttributePrefix : TagHelper + { + [HtmlAttributeName("valid-name1")] + public long LongProperty { get; set; } + + [HtmlAttributeName("valid-name2", DictionaryAttributePrefix = "valid-prefix2-")] + public Dictionary DictionaryOfIntProperty { get; set; } + + [HtmlAttributeName("valid-name3", DictionaryAttributePrefix = "valid-prefix3-")] + public IReadOnlyDictionary ReadOnlyDictionaryProperty { get; set; } + + [HtmlAttributeName("valid-name4", DictionaryAttributePrefix = "valid-prefix4-")] + public int IntProperty { get; set; } + + [HtmlAttributeName("valid-name5", DictionaryAttributePrefix = "valid-prefix5-")] + public DictionaryOfIntSubclass DictionaryOfIntSubclassProperty { get; set; } + + [HtmlAttributeName(DictionaryAttributePrefix = "valid-prefix6")] + public IDictionary GetOnlyDictionaryAttributePrefix { get; } + + [HtmlAttributeName("invalid-name7")] + public IDictionary GetOnlyDictionaryPropertyWithAttributeName { get; } + } + + public class DictionarySubclass : Dictionary + { + } + + public class DictionaryWithoutParameterlessConstructor : Dictionary + { + public DictionaryWithoutParameterlessConstructor(int count) + : base() + { + } + } + + public class DictionaryOfIntSubclass : Dictionary + { + } + + public class GenericDictionarySubclass : Dictionary + { + } + + [OutputElementHint("strong")] + public class OutputElementHintTagHelper : TagHelper + { + } + + [TargetElement("a")] + [TargetElement("p")] + [OutputElementHint("div")] + public class MulitpleDescriptorTagHelperWithOutputElementHint : TagHelper + { + } + + public class TypeDerivingFromITagHelper : ITagHelper + { + public int Order { get; } = 0; + + 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 +{ +}