From 6ff6c53d604a47cb66b52d2eb932452693d1e7ae Mon Sep 17 00:00:00 2001 From: Fabio Cavalcante Date: Fri, 22 Nov 2019 19:32:34 -0800 Subject: [PATCH 01/27] Updating version to beta3 --- .../Targets/Microsoft.NET.Sdk.Functions.Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props index 8fd4c65f..c1495cd5 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.0.30-beta2 + 1.0.30-beta3 From d2e97355c30efad3c47c2f4a69edbab8794f51b1 Mon Sep 17 00:00:00 2001 From: Ankit Kumar Date: Fri, 15 Nov 2019 11:36:15 -0800 Subject: [PATCH 02/27] Update Webjobs and Webjobs Extensions references --- .../Microsoft.NET.Sdk.Functions.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj b/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj index 13fbb9e2..179217b0 100644 --- a/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj +++ b/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj @@ -97,8 +97,8 @@ - - + + From 1922f7d495a8089d8f3293e31dfbca1786e887b0 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Thu, 14 Nov 2019 14:34:07 -0800 Subject: [PATCH 03/27] refactoring build; using Mono.Cecil for attribute discovery --- build.fsx | 4 + .../Microsoft.NET.Sdk.Functions.csproj | 8 ++ .../AttributeExtensions.cs | 33 +++---- .../CustomAttributeDataExtensions.cs | 22 ----- .../FunctionJsonConverter.cs | 55 ++++++----- .../MethodInfoExtensions.cs | 51 +++++----- ...crosoft.NET.Sdk.Functions.Generator.csproj | 2 + .../ParameterInfoExtensions.cs | 16 ++-- .../Program.cs | 17 ++++ .../TypeInfoExtensions.cs | 5 +- .../TypeUtility.cs | 92 +++++++++++++++++-- .../Tasks/GenerateFunctions.cs | 12 +-- .../DisableAttributeTests.cs | 7 +- .../EventHubAttributeTests.cs | 30 ------ .../FunctionJsonConverterTests.cs | 31 +++---- .../GeneralWebJobsAttributesTests.cs | 22 +++-- .../HasUnsupportedAttributesTests.cs | 12 +-- .../HttpTriggerTests.cs | 15 --- .../IConnectionProviderTests.cs | 6 +- ...t.NET.Sdk.Functions.Generator.Tests.csproj | 23 ++++- .../ServiceBusTriggerTests.cs | 24 ----- .../TestUtility.cs | 28 ++++++ .../packages.config | 1 + 23 files changed, 295 insertions(+), 221 deletions(-) delete mode 100644 src/Microsoft.NET.Sdk.Functions.Generator/CustomAttributeDataExtensions.cs delete mode 100644 test/Microsoft.NET.Sdk.Functions.Generator.Tests/EventHubAttributeTests.cs delete mode 100644 test/Microsoft.NET.Sdk.Functions.Generator.Tests/ServiceBusTriggerTests.cs create mode 100644 test/Microsoft.NET.Sdk.Functions.Generator.Tests/TestUtility.cs diff --git a/build.fsx b/build.fsx index dd9b399b..bb898ffb 100644 --- a/build.fsx +++ b/build.fsx @@ -64,6 +64,7 @@ Target "GenerateZipToSign" (fun _ -> |> CreateZip "." (version + "net46.zip") "" 7 true !! (generatorOutputPath @@ "net461\\Newtonsoft.Json.dll") + ++ (generatorOutputPath @@ "net461\\Mono.Cecil.dll") |> CreateZip "." (version + "net46thirdparty.zip") "" 7 true !! (packOutputPath @@ "netstandard2.0\\Microsoft.NET.Sdk.Functions.dll") @@ -72,6 +73,7 @@ Target "GenerateZipToSign" (fun _ -> |> CreateZip "." (version + "netstandard2.zip") "" 7 true !! (generatorOutputPath @@ "netcoreapp2.1\\Newtonsoft.Json.dll") + ++ (generatorOutputPath @@ "netcoreapp2.1\\Mono.Cecil.dll") |> CreateZip "." (version + "netstandard2thidparty.zip") "" 7 true ) @@ -146,6 +148,7 @@ Target "WaitForSigning" (fun _ -> | Success file -> Unzip "tmpBuild" file MoveFileTo ("tmpBuild" @@ "Newtonsoft.Json.dll", generatorOutputPath @@ "net461\\Newtonsoft.Json.dll") + MoveFileTo ("tmpBuild" @@ "Mono.Cecil.dll", generatorOutputPath @@ "net461\\Mono.Cecil.dll") | Failure e -> targetError e null |> ignore CleanDir "tmpBuild" @@ -155,6 +158,7 @@ Target "WaitForSigning" (fun _ -> | Success file -> Unzip "tmpBuild" file MoveFileTo ("tmpBuild" @@ "Newtonsoft.Json.dll", generatorOutputPath @@ "netcoreapp2.1\\Newtonsoft.Json.dll") + MoveFileTo ("tmpBuild" @@ "Mono.Cecil.dll", generatorOutputPath @@ "netcoreapp2.1\\Mono.Cecil.dll") | Failure e -> targetError e null |> ignore ) diff --git a/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj b/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj index 179217b0..94889c4c 100644 --- a/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj +++ b/pack/Microsoft.NET.Sdk.Functions/Microsoft.NET.Sdk.Functions.csproj @@ -63,6 +63,10 @@ true tools\net46\ + + + true + tools\net46\ true @@ -80,6 +84,10 @@ true tools\netcoreapp2.1\ + + + true + tools\netcoreapp2.1\ true diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs index bc570c47..c937da11 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs @@ -1,9 +1,10 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.NET.Sdk.Functions.MakeFunction; +using Mono.Cecil; using Newtonsoft.Json.Linq; -using System.Collections.Generic; namespace MakeFunctionJson { @@ -15,9 +16,18 @@ internal static class AttributeExtensions /// /// public static string ToAttributeFriendlyName(this Attribute attribute) + { + return ToAttributeFriendlyName(attribute.GetType().Name); + } + + public static string ToAttributeFriendlyName(this CustomAttribute attribute) + { + return ToAttributeFriendlyName(attribute.AttributeType.Name); + } + + private static string ToAttributeFriendlyName(string name) { const string suffix = nameof(Attribute); - var name = attribute.GetType().Name; name = name.Substring(0, name.Length - suffix.Length); return name.ToLowerFirstCharacter(); } @@ -37,15 +47,10 @@ public static string ToAttributeFriendlyName(this Attribute attribute) /// /// /// - public static bool IsWebJobsAttribute(this Attribute attribute) + public static bool IsWebJobsAttribute(this CustomAttribute attribute) { -#if NET46 - return attribute.GetType().GetCustomAttributes().Any(a => a.GetType().FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute") - || _supportedAttributes.Contains(attribute.GetType().Name); -#else - return attribute.GetType().GetTypeInfo().GetCustomAttributesData().Any(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute") - || _supportedAttributes.Contains(attribute.GetType().Name); -#endif + return attribute.AttributeType.Resolve().CustomAttributes.Any(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.Description.BindingAttribute") + || _supportedAttributes.Contains(attribute.AttributeType.FullName); } /// @@ -211,14 +216,6 @@ private static string NormalizePropertyName(Attribute attribute, PropertyInfo pr return "schedule"; } } - else if (attributeName == "EventHubTriggerAttribute" && - attribute.GetType().Assembly.GetName().Version.Major == 2) - { - if (propertyName == "EventHubName") - { - return "path"; - } - } else if (attributeName == "ApiHubFileTrigger") { if (propertyName == "ConnectionStringSetting") diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/CustomAttributeDataExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/CustomAttributeDataExtensions.cs deleted file mode 100644 index 1567fbe1..00000000 --- a/src/Microsoft.NET.Sdk.Functions.Generator/CustomAttributeDataExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Linq; -using System.Reflection; - -namespace Microsoft.NET.Sdk.Functions.Generator -{ - public static class CustomAttributeDataExtensions - { - public static Attribute ConvertToAttribute(this CustomAttributeData data) - { - var attribute = data.Constructor.Invoke(data.ConstructorArguments.Select(arg => arg.Value).ToArray()) as Attribute; - - foreach (var namedArgument in data.NamedArguments) - { - (namedArgument.MemberInfo as PropertyInfo)?.SetValue(attribute, namedArgument.TypedValue.Value, null); - (namedArgument.MemberInfo as FieldInfo)?.SetValue(attribute, namedArgument.TypedValue.Value); - } - - return attribute; - } - } -} diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/FunctionJsonConverter.cs b/src/Microsoft.NET.Sdk.Functions.Generator/FunctionJsonConverter.cs index 14cb5199..27364218 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/FunctionJsonConverter.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/FunctionJsonConverter.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Reflection; +using Mono.Cecil; using Newtonsoft.Json; namespace MakeFunctionJson @@ -14,7 +15,7 @@ internal class FunctionJsonConverter private bool _functionsInDependencies; private readonly HashSet _excludedFunctionNames; private readonly ILogger _logger; - private readonly IDictionary _functionNamesSet; + private readonly IDictionary _functionNamesSet; private static readonly IEnumerable _functionsArtifacts = new[] { @@ -34,7 +35,7 @@ internal FunctionJsonConverter(ILogger logger, string assemblyPath, string outpu { throw new ArgumentNullException(nameof(logger)); } - + if (string.IsNullOrEmpty(assemblyPath)) { throw new ArgumentNullException(nameof(assemblyPath)); @@ -54,7 +55,7 @@ internal FunctionJsonConverter(ILogger logger, string assemblyPath, string outpu { _outputPath = Path.Combine(Directory.GetCurrentDirectory(), _outputPath); } - _functionNamesSet = new Dictionary(StringComparer.OrdinalIgnoreCase); + _functionNamesSet = new Dictionary(StringComparer.OrdinalIgnoreCase); } /// @@ -111,11 +112,11 @@ private void CopyFunctionArtifacts() } } - public IEnumerable<(FunctionJsonSchema schema, FileInfo outputFile)?> GenerateFunctions(IEnumerable types) + public IEnumerable<(FunctionJsonSchema schema, FileInfo outputFile)?> GenerateFunctions(IEnumerable types) { foreach (var type in types) { - foreach (var method in type.GetMethods()) + foreach (var method in type.Methods) { if (method.HasFunctionNameAttribute()) { @@ -129,7 +130,7 @@ private void CopyFunctionArtifacts() var functionName = method.GetSdkFunctionName(); var artifactName = Path.Combine(functionName, "function.json"); var path = Path.Combine(_outputPath, artifactName); - var relativeAssemblyPath = PathUtility.MakeRelativePath(Path.Combine(_outputPath, "dummyFunctionName"), type.Assembly.Location); + var relativeAssemblyPath = PathUtility.MakeRelativePath(Path.Combine(_outputPath, "dummyFunctionName"), type.Module.FileName); var functionJson = method.ToFunctionJson(relativeAssemblyPath); if (CheckAppSettingsAndFunctionName(functionJson, method)) { @@ -144,42 +145,54 @@ private void CopyFunctionArtifacts() { if (method.HasNoAutomaticTriggerAttribute() && method.HasTriggerAttribute()) { - _logger.LogWarning($"Method {method.ReflectedType?.FullName}.{method.Name} has both a 'NoAutomaticTrigger' attribute and a trigger attribute. Both can't be used together for an Azure function definition."); + _logger.LogWarning($"Method {method.DeclaringType.GetReflectionFullName()}.{method.Name} has both a 'NoAutomaticTrigger' attribute and a trigger attribute. Both can't be used together for an Azure function definition."); } else { - _logger.LogWarning($"Method {method.ReflectedType?.FullName}.{method.Name} is missing a trigger attribute. Both a trigger attribute and FunctionName attribute are required for an Azure function definition."); + _logger.LogWarning($"Method {method.DeclaringType.GetReflectionFullName()}.{method.Name} is missing a trigger attribute. Both a trigger attribute and FunctionName attribute are required for an Azure function definition."); } } else if (method.HasValidWebJobSdkTriggerAttribute()) { - _logger.LogWarning($"Method {method.ReflectedType?.FullName}.{method.Name} is missing the 'FunctionName' attribute. Both a trigger attribute and 'FunctionName' are required for an Azure function definition."); + _logger.LogWarning($"Method {method.DeclaringType.GetReflectionFullName()}.{method.Name} is missing the 'FunctionName' attribute. Both a trigger attribute and 'FunctionName' are required for an Azure function definition."); } } } } } - + private bool TryGenerateFunctionJsons() { - var assembly = Assembly.LoadFrom(_assemblyPath); var assemblyRoot = Path.GetDirectoryName(_assemblyPath); - var exportedTypes = assembly.ExportedTypes; + var resolver = new DefaultAssemblyResolver(); + resolver.AddSearchDirectory(assemblyRoot); + + var readerParams = new ReaderParameters + { + AssemblyResolver = resolver + }; + + var module = ModuleDefinition.ReadModule(_assemblyPath, readerParams); + + IEnumerable exportedTypes = module.Types; if (_functionsInDependencies) { - foreach (var referencedAssembly in assembly.GetReferencedAssemblies()) + foreach (var referencedAssembly in module.AssemblyReferences) { var tryPath = Path.Combine(assemblyRoot, $"{referencedAssembly.Name}.dll"); - try + if (File.Exists(tryPath)) { - var loadedAssembly = Assembly.LoadFrom(tryPath); - exportedTypes = exportedTypes.Concat(loadedAssembly.ExportedTypes); - } - catch (Exception ex) - { - _logger.LogWarning($"Could not evaluate '{referencedAssembly.Name}' for function types. Exception message: {ex.Message}"); + try + { + var loadedModule = ModuleDefinition.ReadModule(tryPath, readerParams); + exportedTypes = exportedTypes.Concat(loadedModule.Types); + } + catch (Exception ex) + { + _logger.LogWarning($"Could not evaluate '{referencedAssembly.Name}' for function types. Exception message: {ex.Message}"); + } } } } @@ -192,7 +205,7 @@ private bool TryGenerateFunctionJsons() return functions.All(f => f.HasValue); } - private bool CheckAppSettingsAndFunctionName(FunctionJsonSchema functionJson, MethodInfo method) + private bool CheckAppSettingsAndFunctionName(FunctionJsonSchema functionJson, MethodDefinition method) { try { diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/MethodInfoExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/MethodInfoExtensions.cs index f1e45fed..98b5804b 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/MethodInfoExtensions.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/MethodInfoExtensions.cs @@ -1,7 +1,7 @@ using System; using System.Linq; using System.Reflection; -using Microsoft.NET.Sdk.Functions.Generator; +using Mono.Cecil; using Newtonsoft.Json.Linq; namespace MakeFunctionJson @@ -13,37 +13,37 @@ internal static class MethodInfoExtensions /// /// method to check if an SDK method or not. /// true if is a WebJobs SDK method. False otherwise. - public static bool IsWebJobsSdkMethod(this MethodInfo method) + public static bool IsWebJobsSdkMethod(this MethodDefinition method) { return method.HasFunctionNameAttribute() && method.HasValidWebJobSdkTriggerAttribute(); } - public static bool HasValidWebJobSdkTriggerAttribute(this MethodInfo method) + public static bool HasValidWebJobSdkTriggerAttribute(this MethodDefinition method) { var hasNoAutomaticTrigger = method.HasNoAutomaticTriggerAttribute(); var hasTrigger = method.HasTriggerAttribute(); return (hasNoAutomaticTrigger || hasTrigger) && !(hasNoAutomaticTrigger && hasTrigger); } - public static bool HasFunctionNameAttribute(this MethodInfo method) + public static bool HasFunctionNameAttribute(this MethodDefinition method) { - return method.GetCustomAttributesData().FirstOrDefault(d => d.AttributeType.FullName == "Microsoft.Azure.WebJobs.FunctionNameAttribute") != null; + return method.CustomAttributes.FirstOrDefault(d => d.AttributeType.FullName == "Microsoft.Azure.WebJobs.FunctionNameAttribute") != null; } - public static bool HasNoAutomaticTriggerAttribute(this MethodInfo method) + public static bool HasNoAutomaticTriggerAttribute(this MethodDefinition method) { - return method.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.NoAutomaticTriggerAttribute") != null; + return method.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.NoAutomaticTriggerAttribute") != null; } - public static bool HasTriggerAttribute(this MethodInfo method) + public static bool HasTriggerAttribute(this MethodDefinition method) { - return method.GetParameters().Any(p => p.IsWebJobSdkTriggerParameter()); + return method.Parameters.Any(p => p.IsWebJobSdkTriggerParameter()); } - public static JObject ManualTriggerBinding(this MethodInfo method) + public static JObject ManualTriggerBinding(this MethodDefinition method) { var binding = new JObject { ["type"] = "manualTrigger", ["direction"] = "in" }; - var stringParameter = method.GetParameters().FirstOrDefault(p => p.ParameterType == typeof(string)); + var stringParameter = method.Parameters.FirstOrDefault(p => p.ParameterType.FullName == typeof(string).FullName); if (stringParameter != null) { binding["name"] = stringParameter.Name; @@ -57,13 +57,13 @@ public static JObject ManualTriggerBinding(this MethodInfo method) /// method to convert to a object. The method has to be /// This will be the value of on the returned value. /// object that represents the passed in . - public static FunctionJsonSchema ToFunctionJson(this MethodInfo method, string assemblyPath) + public static FunctionJsonSchema ToFunctionJson(this MethodDefinition method, string assemblyPath) { return new FunctionJsonSchema { // For every SDK parameter, convert it to a FunctionJson bindings. // Every parameter can potentially contain more than 1 attribute that will be converted into a binding object. - Bindings = method.HasNoAutomaticTriggerAttribute() ? new[] { method.ManualTriggerBinding() } : method.GetParameters() + Bindings = method.HasNoAutomaticTriggerAttribute() ? new[] { method.ManualTriggerBinding() } : method.Parameters .Where(p => p.IsWebJobSdkTriggerParameter()) .Select(p => p.ToFunctionJsonBindings()) .SelectMany(i => i) @@ -82,17 +82,17 @@ public static FunctionJsonSchema ToFunctionJson(this MethodInfo method, string a /// /// method has to be a WebJobs SDK method. /// Function name. - public static string GetSdkFunctionName(this MethodInfo method) + public static string GetSdkFunctionName(this MethodDefinition method) { if (!method.IsWebJobsSdkMethod()) { throw new ArgumentException($"{nameof(method)} has to be a WebJob SDK function"); } - var functionNameAttribute = method.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.Name == "FunctionNameAttribute")?.ConvertToAttribute(); - if (functionNameAttribute != null) + string functionName = method.CustomAttributes.FirstOrDefault(a => a.AttributeType.Name == "FunctionNameAttribute")?.ConstructorArguments[0].Value.ToString(); + if (functionName != null) { - return functionNameAttribute.GetType().GetProperty("Name").GetValue(functionNameAttribute).ToString(); + return functionName; } else { @@ -107,13 +107,16 @@ public static string GetSdkFunctionName(this MethodInfo method) /// /// /// a boolean true or false if the outcome is fixed, a string if the ScriptHost should interpret it - public static object GetDisabled(this MethodInfo method) + public static object GetDisabled(this MethodDefinition method) { - var attribute = method.GetParameters().Select(p => p.GetDisabledAttribute()).Where(a => a != null).FirstOrDefault() ?? + var customAttribute = method.Parameters.Select(p => p.GetDisabledAttribute()).Where(a => a != null).FirstOrDefault() ?? method.GetDisabledAttribute() ?? - method.DeclaringType.GetTypeInfo().GetDisabledAttribute(); - if (attribute != null) + method.DeclaringType.GetDisabledAttribute(); + + if (customAttribute != null) { + var attribute = customAttribute.ToReflection(); + // With a SettingName defined, just put that as string. The ScriptHost will evaluate it. var settingName = attribute.GetValue("SettingName"); if (!string.IsNullOrEmpty(settingName)) @@ -141,9 +144,9 @@ public static object GetDisabled(this MethodInfo method) /// /// /// - public static Attribute GetDisabledAttribute(this MethodInfo method) + public static CustomAttribute GetDisabledAttribute(this MethodDefinition method) { - return method.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute")?.ConvertToAttribute(); + return method.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute"); } /// @@ -155,7 +158,7 @@ public static Attribute GetDisabledAttribute(this MethodInfo method) /// /// /// - public static bool HasUnsuportedAttributes(this MethodInfo method, out string error) + public static bool HasUnsuportedAttributes(this MethodDefinition method, out string error) { error = string.Empty; var disabled = method.GetDisabled(); diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/Microsoft.NET.Sdk.Functions.Generator.csproj b/src/Microsoft.NET.Sdk.Functions.Generator/Microsoft.NET.Sdk.Functions.Generator.csproj index de6ee8a0..7b4c22e5 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/Microsoft.NET.Sdk.Functions.Generator.csproj +++ b/src/Microsoft.NET.Sdk.Functions.Generator/Microsoft.NET.Sdk.Functions.Generator.csproj @@ -18,11 +18,13 @@ + + diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/ParameterInfoExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/ParameterInfoExtensions.cs index 06d9c7f6..0f5c3ecd 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/ParameterInfoExtensions.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/ParameterInfoExtensions.cs @@ -1,8 +1,7 @@ using System.Collections.Generic; using System.Linq; -using System.Reflection; +using Mono.Cecil; using Newtonsoft.Json.Linq; -using System; namespace MakeFunctionJson { @@ -13,10 +12,10 @@ internal static class ParameterInfoExtensions /// /// /// - public static bool IsWebJobSdkTriggerParameter(this ParameterInfo parameterInfo) + public static bool IsWebJobSdkTriggerParameter(this ParameterDefinition parameterInfo) { return parameterInfo - .GetCustomAttributes() + .CustomAttributes .Any(a => a.IsWebJobsAttribute() && a.ToAttributeFriendlyName().IndexOf("Trigger") > -1); } @@ -25,11 +24,10 @@ public static bool IsWebJobSdkTriggerParameter(this ParameterInfo parameterInfo) /// /// Has to be a WebJobSdkParameter /// - public static IEnumerable ToFunctionJsonBindings(this ParameterInfo parameterInfo) + public static IEnumerable ToFunctionJsonBindings(this ParameterDefinition parameterInfo) { - return parameterInfo - .GetCustomAttributes() + .CustomAttributes .Where(a => a.IsWebJobsAttribute()) // this has to return at least 1. .Select(a => TypeUtility.GetResolvedAttribute(parameterInfo, a)) // For IConnectionProvider logic. .Select(a => a.ToJObject()) // Convert the Attribute into a JObject. @@ -47,9 +45,9 @@ public static IEnumerable ToFunctionJsonBindings(this ParameterInfo par /// /// /// - public static Attribute GetDisabledAttribute(this ParameterInfo parameterInfo) + public static CustomAttribute GetDisabledAttribute(this ParameterDefinition parameterInfo) { - return parameterInfo.GetCustomAttributes().FirstOrDefault(a => a.GetType().FullName == "Microsoft.Azure.WebJobs.DisableAttribute"); + return parameterInfo.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute"); } } } diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/Program.cs b/src/Microsoft.NET.Sdk.Functions.Generator/Program.cs index d7fe4019..0fb42d01 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/Program.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/Program.cs @@ -1,6 +1,11 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Reflection; +#if NETCOREAPP2_1 +using System.Runtime.Loader; +#endif using MakeFunctionJson; namespace Microsoft.NET.Sdk.Functions.Console @@ -19,7 +24,19 @@ private static void Main(string[] args) var assemblyPath = args[0].Trim(); var outputPath = args[1].Trim(); var functionsInDependencies = bool.Parse(args[2].Trim()); + var assemblyDir = Path.GetDirectoryName(assemblyPath); +#if NETCOREAPP2_1 + AssemblyLoadContext.Default.Resolving += (context, assemblyName) => + { + return context.LoadFromAssemblyPath(Path.Combine(assemblyDir, assemblyName.Name + ".dll")); + }; +#else + AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => + { + return Assembly.LoadFrom(Path.Combine(assemblyDir, e.Name + ".dll")); + }; +#endif IEnumerable excludedFunctionNames = Enumerable.Empty(); if (args.Length > 2) diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/TypeInfoExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/TypeInfoExtensions.cs index 3ca750ec..f6e3a873 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/TypeInfoExtensions.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/TypeInfoExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Linq; using System.Reflection; +using Mono.Cecil; namespace MakeFunctionJson { @@ -11,9 +12,9 @@ public static bool IsImplementing(this TypeInfo typeInfo, string interfaceName) return typeInfo.ImplementedInterfaces.Any(i => i.Name.Equals(interfaceName, StringComparison.OrdinalIgnoreCase)); } - public static Attribute GetDisabledAttribute(this TypeInfo type) + public static CustomAttribute GetDisabledAttribute(this TypeDefinition type) { - return type.GetCustomAttributes().FirstOrDefault(a => a.GetType().FullName == "Microsoft.Azure.WebJobs.DisableAttribute"); + return type.CustomAttributes.FirstOrDefault(a => a.AttributeType.FullName == "Microsoft.Azure.WebJobs.DisableAttribute"); } } } diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/TypeUtility.cs b/src/Microsoft.NET.Sdk.Functions.Generator/TypeUtility.cs index 8d007f03..415e33ab 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/TypeUtility.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/TypeUtility.cs @@ -1,6 +1,11 @@ using System; +using System.IO; using System.Linq; using System.Reflection; +#if NETCOREAPP2_1 +using System.Runtime.Loader; +#endif +using Mono.Cecil; namespace MakeFunctionJson { @@ -15,7 +20,7 @@ internal static class TypeUtility /// /// The parameter to check. /// The attribute type to look for. - private static Attribute GetHierarchicalAttributeOrNull(ParameterInfo parameter, Type attributeType) + private static CustomAttribute GetHierarchicalAttributeOrNull(ParameterDefinition parameter, Type attributeType) { if (parameter == null) { @@ -28,7 +33,7 @@ private static Attribute GetHierarchicalAttributeOrNull(ParameterInfo parameter, return attribute; } - var method = parameter.Member as MethodInfo; + var method = parameter.Method as MethodDefinition; if (method == null) { return null; @@ -42,7 +47,7 @@ private static Attribute GetHierarchicalAttributeOrNull(ParameterInfo parameter, /// /// The method to check. /// The attribute type to look for. - private static Attribute GetHierarchicalAttributeOrNull(MethodInfo method, Type type) + private static CustomAttribute GetHierarchicalAttributeOrNull(Mono.Cecil.MethodDefinition method, Type type) { var attribute = method.GetCustomAttribute(type); if (attribute != null) @@ -50,7 +55,7 @@ private static Attribute GetHierarchicalAttributeOrNull(MethodInfo method, Type return attribute; } - attribute = method.DeclaringType.GetTypeInfo().GetCustomAttribute(type); + attribute = method.DeclaringType.GetCustomAttribute(type); if (attribute != null) { return attribute; @@ -59,8 +64,10 @@ private static Attribute GetHierarchicalAttributeOrNull(MethodInfo method, Type return null; } - internal static Attribute GetResolvedAttribute(ParameterInfo parameter, Attribute attribute) + internal static Attribute GetResolvedAttribute(ParameterDefinition parameter, CustomAttribute customAttribute) { + Attribute attribute = customAttribute.ToReflection(); + if (attribute != null && attribute.GetType().GetTypeInfo().IsImplementing("IConnectionProvider") && string.IsNullOrEmpty(attribute.GetValue("Connection"))) @@ -75,14 +82,14 @@ internal static Attribute GetResolvedAttribute(ParameterInfo parameter, Attribut if (connectionProviderAttribute?.GetValue("ProviderType") != null) { - var connectionOverrideProvider = GetHierarchicalAttributeOrNull(parameter, connectionProviderAttribute.GetValue("ProviderType")); + var connectionOverrideProvider = GetHierarchicalAttributeOrNull(parameter, connectionProviderAttribute.GetValue("ProviderType"))?.ToReflection(); if (connectionOverrideProvider != null && connectionOverrideProvider.GetType().GetTypeInfo().IsImplementing("IConnectionProvider")) { var iConnectionProvider = connectionOverrideProvider.GetType().GetTypeInfo().GetInterface("IConnectionProvider"); var propertyInfo = iConnectionProvider.GetProperty("Connection"); - var connectionValue = (string) propertyInfo.GetValue(attribute); - connectionValue = connectionValue + var connectionValue = (string)propertyInfo.GetValue(attribute); + connectionValue = connectionValue ?? connectionOverrideProvider.GetValue("Connection") ?? connectionOverrideProvider.GetValue("Account"); if (!string.IsNullOrEmpty(connectionValue)) @@ -95,5 +102,74 @@ internal static Attribute GetResolvedAttribute(ParameterInfo parameter, Attribut return attribute; } + + public static Attribute ToReflection(this CustomAttribute customAttribute) + { + var attributeType = customAttribute.AttributeType.ToReflectionType(); + + Type[] constructorParams = customAttribute.Constructor.Parameters + .Select(p => p.ParameterType.ToReflectionType()) + .ToArray(); + + Attribute attribute = attributeType.GetConstructor(constructorParams) + .Invoke(customAttribute.ConstructorArguments.Select(p => NormalizeArg(p)).ToArray()) as Attribute; + + foreach (var namedArgument in customAttribute.Properties) + { + attributeType.GetProperty(namedArgument.Name)?.SetValue(attribute, namedArgument.Argument.Value); + attributeType.GetField(namedArgument.Name)?.SetValue(attribute, namedArgument.Argument.Value); + } + + return attribute; + } + + public static Type ToReflectionType(this TypeReference typeDef) + { + Type t = Type.GetType(typeDef.GetReflectionFullName()); + + if (t == null) + { +#if NETCOREAPP2_1 + Assembly a = AssemblyLoadContext.Default.LoadFromAssemblyPath(Path.GetFullPath(typeDef.Resolve().Module.FileName)); +#else + Assembly a = Assembly.LoadFrom(Path.GetFullPath(typeDef.Resolve().Module.FileName)); +#endif + t = a.GetType(typeDef.GetReflectionFullName()); + } + + return t; + } + + private static object NormalizeArg(CustomAttributeArgument arg) + { + if (arg.Type.IsArray) + { + var arguments = arg.Value as CustomAttributeArgument[]; + Type arrayType = arg.Type.GetElementType().ToReflectionType(); + var array = Array.CreateInstance(arrayType, arguments.Length); + for (int i = 0; i < array.Length; i++) + { + array.SetValue(arguments[i].Value, i); + } + return array; + } + + if (arg.Value is TypeDefinition typeDef) + { + return typeDef.ToReflectionType(); + } + + return arg.Value; + } + + public static CustomAttribute GetCustomAttribute(this Mono.Cecil.ICustomAttributeProvider provider, Type parameterType) + { + return provider.CustomAttributes.SingleOrDefault(p => p.AttributeType.FullName == parameterType.FullName); + } + + public static string GetReflectionFullName(this TypeReference typeRef) + { + return typeRef.FullName.Replace("/", "+"); + } } } diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Tasks/GenerateFunctions.cs b/src/Microsoft.NET.Sdk.Functions.MSBuild/Tasks/GenerateFunctions.cs index b135d37e..41fc0478 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Tasks/GenerateFunctions.cs +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Tasks/GenerateFunctions.cs @@ -31,7 +31,7 @@ public class GenerateFunctions : Task public bool GenerateHostJson { get; set; } public ITaskItem[] UserProvidedFunctionJsonFiles { get; set; } - + public bool FunctionsInDependencies { get; set; } public override bool Execute() @@ -44,7 +44,7 @@ public override bool Execute() } string taskAssemblyDirectory = Path.GetDirectoryName(typeof(GenerateFunctions).GetTypeInfo().Assembly.Location); - string baseDirectory = Path.GetDirectoryName(taskAssemblyDirectory); + string baseDirectory = Path.GetDirectoryName(taskAssemblyDirectory); ProcessStartInfo processStartInfo = null; #if NET46 processStartInfo = GetProcessStartInfo(baseDirectory, isCore: false); @@ -68,9 +68,9 @@ public override bool Execute() var output = process.StandardOutput.ReadToEnd(); var error = process.StandardError.ReadToEnd(); process.WaitForExit(); - - if (!string.IsNullOrEmpty(output)) - { + + if (!string.IsNullOrEmpty(output)) + { Log.LogWarning(output); } @@ -111,7 +111,7 @@ private ProcessStartInfo GetProcessStartInfo(string baseLocation, bool isCore) RedirectStandardOutput = true, WorkingDirectory = workingDirectory, FileName = exePath, - Arguments = arguments + Arguments = arguments }; } } diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/DisableAttributeTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/DisableAttributeTests.cs index 3f312a72..f24b586a 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/DisableAttributeTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/DisableAttributeTests.cs @@ -1,9 +1,10 @@ using System; +using System.Reflection; using FluentAssertions; using MakeFunctionJson; using Microsoft.Azure.WebJobs; +using Mono.Cecil; using Xunit; -using System.Reflection; namespace Microsoft.NET.Sdk.Functions.Test { @@ -53,8 +54,8 @@ public static void Run([QueueTrigger("")] string message) { } [InlineData(typeof(FunctionsClass3), "Run", false)] public void MethodsWithDisabledParametersShouldBeDisabled(Type type, string methodName, object expectedIsDisabled) { - var method = type.GetMethod(methodName); - var funcJson = method.ToFunctionJson(string.Empty); + MethodDefinition methodDef = TestUtility.GetMethodDefinition(type, methodName); + FunctionJsonSchema funcJson = methodDef.ToFunctionJson(string.Empty); funcJson.Disabled.Should().Be(expectedIsDisabled); } } diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/EventHubAttributeTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/EventHubAttributeTests.cs deleted file mode 100644 index 527ce49e..00000000 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/EventHubAttributeTests.cs +++ /dev/null @@ -1,30 +0,0 @@ -using FluentAssertions; -using FluentAssertions.Json; -using MakeFunctionJson; -using Microsoft.Azure.WebJobs.ServiceBus; -using Xunit; - -namespace Microsoft.NET.Sdk.Functions.Test -{ - public class EventHubAttributeTests - { - [Fact] - public static void EventHubTriggerAttribute_ShouldHaveV1vsV2Differences() - { - var attribute = new EventHubTriggerAttribute("eventHub"); - - var jObject = attribute.ToJObject(); - -#if NET46 - jObject.Should().HaveElement("path"); - jObject["path"].Should().Be("eventHub"); -#else - jObject.Should().HaveElement("type"); - jObject["type"].Should().Be("eventHubTrigger"); - - jObject.Should().HaveElement("path"); - jObject["path"].Should().Be("eventHub"); -#endif - } - } -} diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs index 1aaed967..8d7828a7 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs @@ -4,9 +4,8 @@ using System.Net.Http; using FluentAssertions; using MakeFunctionJson; +using Microsoft.Azure.EventHubs; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.ServiceBus; -using Microsoft.ServiceBus.Messaging; using Microsoft.WindowsAzure.Storage.Queue; using Xunit; @@ -18,25 +17,25 @@ public class FunctionsClass { [FunctionName("MyHttpTrigger")] public static void Run1([HttpTrigger] HttpRequestMessage request) { } - + [FunctionName("MyBlobTrigger")] public static void Run2([BlobTrigger("blob.txt")] string blobContent) { } - + [FunctionName("MyQueueTrigger")] public static void Run3([QueueTrigger("queue")] CloudQueue queue) { } - + [FunctionName("MyEventHubTrigger")] public static void Run4([EventHubTrigger("hub")] EventData message) { } - + [FunctionName("MyTimerTrigger")] public static void Run5([TimerTrigger("00:30:00")] TimerInfo timer) { } - + [FunctionName("MyServiceBusTrigger")] public static void Run6([ServiceBusTrigger("queue")] string message) { } - + [FunctionName("MyManualTrigger"), NoAutomaticTrigger] public static void Run7(string input) { } - + [FunctionName("MyManualTriggerWithoutParameters"), NoAutomaticTrigger] public static void Run8() { } } @@ -53,16 +52,16 @@ public static void Run8() { } public void FunctionMethodsAreExported(string functionName, string type, string parameterName) { var logger = new RecorderLogger(); - var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies:false); - var functions = converter.GenerateFunctions(new [] {typeof(FunctionsClass)}); + var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies: false); + var functions = converter.GenerateFunctions(new[] { TestUtility.GetTypeDefinition(typeof(FunctionsClass)) }); var schema = functions.Single(e => Path.GetFileName(e.Value.outputFile.DirectoryName) == functionName).Value.schema; - var binding = schema.Bindings.Single(); + var binding = schema.Bindings.Single(); binding.Value("type").Should().Be(type); binding.Value("name").Should().Be(parameterName); logger.Errors.Should().BeEmpty(); logger.Warnings.Should().BeEmpty(); } - + public class InvalidFunctionBecauseOfMissingTrigger { [FunctionName("MyServiceBusTrigger")] @@ -79,7 +78,7 @@ public class InvalidFunctionBecauseOfBothNoAutomaticTriggerAndServiceBusTrigger [FunctionName("MyServiceBusTrigger"), NoAutomaticTrigger] public static void Run([ServiceBusTrigger("queue")] string message) { } } - + [Theory] [InlineData(typeof(InvalidFunctionBecauseOfMissingTrigger), "Method Microsoft.NET.Sdk.Functions.Test.FunctionJsonConverterTests+InvalidFunctionBecauseOfMissingTrigger.Run is missing a trigger attribute. Both a trigger attribute and FunctionName attribute are required for an Azure function definition.")] // [InlineData(typeof(InvalidFunctionBecauseOfMissingFunctionName), "Method Microsoft.NET.Sdk.Functions.Test.FunctionJsonConverterTests+InvalidFunctionBecauseOfMissingFunctionName.Run is missing the 'FunctionName' attribute. Both a trigger attribute and 'FunctionName' are required for an Azure function definition.")] @@ -87,8 +86,8 @@ public static void Run([ServiceBusTrigger("queue")] string message) { } public void InvalidFunctionMethodProducesWarning(Type type, string warningMessage) { var logger = new RecorderLogger(); - var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies:false); - var functions = converter.GenerateFunctions(new [] {type}); + var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies: false); + var functions = converter.GenerateFunctions(new[] { TestUtility.GetTypeDefinition(type) }); functions.Should().BeEmpty(); logger.Errors.Should().BeEmpty(); logger.Warnings.Should().ContainSingle(); diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/GeneralWebJobsAttributesTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/GeneralWebJobsAttributesTests.cs index 8c11641e..5b048ab6 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/GeneralWebJobsAttributesTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/GeneralWebJobsAttributesTests.cs @@ -1,26 +1,32 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; +using System.Linq; using FluentAssertions; using MakeFunctionJson; using Microsoft.Azure.WebJobs; -using Microsoft.Azure.WebJobs.ServiceBus; +using Mono.Cecil; using Xunit; namespace Microsoft.NET.Sdk.Functions.Test { public class GeneralWebJobsAttributesTests { + private static void FakeFunction( + [QueueTrigger("a")] + [BlobTrigger("b")] + [EventHubTrigger("c")] + [ServiceBusTrigger("d")] string abcd) + { + } + public static IEnumerable GetAttributes() { - yield return new object[] { new QueueTriggerAttribute(string.Empty) }; - yield return new object[] { new BlobTriggerAttribute(string.Empty) }; - yield return new object[] { new EventHubTriggerAttribute(string.Empty) }; - yield return new object[] { new ServiceBusTriggerAttribute(string.Empty) }; + return TestUtility.GetCustomAttributes(typeof(GeneralWebJobsAttributesTests), "FakeFunction", "abcd") + .Select(p => new object[] { p }); } [Theory] [MemberData(nameof(GetAttributes))] - public void IsWebJobsAttribute(Attribute attribute) + public void IsWebJobsAttribute(CustomAttribute attribute) { attribute.IsWebJobsAttribute().Should().BeTrue(because: $"{attribute.GetType().FullName} is a WebJob's attribute"); } diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HasUnsupportedAttributesTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HasUnsupportedAttributesTests.cs index 53d82855..0c6b07ab 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HasUnsupportedAttributesTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HasUnsupportedAttributesTests.cs @@ -1,12 +1,8 @@ -using FluentAssertions; +using System; +using System.Reflection; +using FluentAssertions; using MakeFunctionJson; using Microsoft.Azure.WebJobs; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Microsoft.NET.Sdk.Functions.Test @@ -43,7 +39,7 @@ public static void Run6([QueueTrigger("")] string message) { } [InlineData(typeof(FunctionsClass1), "Run6", true)] public void HasUnsupportedAttributesWorksCorrectly(Type type, string methodName, bool expected) { - var method = type.GetMethod(methodName); + var method = TestUtility.GetMethodDefinition(type, methodName); var hasUnsuportedAttribute = method.HasUnsuportedAttributes(out string _); hasUnsuportedAttribute.Should().Be(expected); } diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HttpTriggerTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HttpTriggerTests.cs index 10ed01c4..8b972f93 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HttpTriggerTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/HttpTriggerTests.cs @@ -3,8 +3,6 @@ using MakeFunctionJson; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; -using System; -using System.Linq; using Xunit; namespace Microsoft.NET.Sdk.Functions.Test @@ -22,18 +20,5 @@ public void HttpTriggerShouldHaveStringForAuthLevelEnum() jObject.Should().HaveElement("authLevel"); jObject["authLevel"].Should().Be("function"); } - - [Fact] - public void HttpTriggerAttributeWithWebHookTypeShouldntHaveAnAuthLevel() - { - var attribute = new HttpTriggerAttribute() - { - WebHookType = "something" - }; - - var jObject = attribute.ToJObject(); - - jObject["authLevel"].Should().BeNull(); - } } } diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/IConnectionProviderTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/IConnectionProviderTests.cs index f755c59e..33327f8d 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/IConnectionProviderTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/IConnectionProviderTests.cs @@ -55,8 +55,10 @@ public class IConnectionProviderTests [InlineData(typeof(FunctionsClass4), "foobarfoobar")] public void TestIConnectionProviderHierarchicalLogic(Type type, string expected) { - var parameterInfo = type.GetMethod("Run").GetParameters().First(); - var attribute = (Attribute) parameterInfo.GetCustomAttributes(typeof(QueueTriggerAttribute), false).First(); + var method = TestUtility.GetMethodDefinition(type, "Run"); + + var parameterInfo = method.Parameters.First(); + var attribute = parameterInfo.GetCustomAttribute(typeof(QueueTriggerAttribute)); var resolvedAttribute = TypeUtility.GetResolvedAttribute(parameterInfo, attribute); diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/Microsoft.NET.Sdk.Functions.Generator.Tests.csproj b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/Microsoft.NET.Sdk.Functions.Generator.Tests.csproj index c23874d2..765a1828 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/Microsoft.NET.Sdk.Functions.Generator.Tests.csproj +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/Microsoft.NET.Sdk.Functions.Generator.Tests.csproj @@ -125,12 +125,21 @@ ..\..\packages\WindowsAzure.Storage.8.6.0\lib\net45\Microsoft.WindowsAzure.Storage.dll + + ..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.dll + + + ..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Mdb.dll + + + ..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Pdb.dll + + + ..\..\packages\Mono.Cecil.0.11.1\lib\net40\Mono.Cecil.Rocks.dll + ..\..\packages\ncrontab.3.3.0\lib\net35\NCrontab.dll - - ..\..\packages\Newtonsoft.Json.11.0.2\lib\net45\Newtonsoft.Json.dll - ..\..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll @@ -225,18 +234,22 @@ ..\..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll + + + + + - - + diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/ServiceBusTriggerTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/ServiceBusTriggerTests.cs deleted file mode 100644 index 5a944b1f..00000000 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/ServiceBusTriggerTests.cs +++ /dev/null @@ -1,24 +0,0 @@ -using FluentAssertions; -using FluentAssertions.Json; -using MakeFunctionJson; -using Microsoft.Azure.WebJobs; -using Microsoft.ServiceBus.Messaging; -using Xunit; - -namespace Microsoft.NET.Sdk.Functions.Test -{ - public class ServiceBusTriggerTests - { - [Fact] - // https://github.com/Azure/azure-functions-vs-build-sdk/issues/1 - public void ServiceBusTriggerShouldHaveStringEnumForAccessRights() - { - var attribute = new ServiceBusTriggerAttribute("queue1", AccessRights.Manage); - - var jObject = attribute.ToJObject(); - - jObject.Should().HaveElement("accessRights"); - jObject["accessRights"].Should().Be("manage"); - } - } -} diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/TestUtility.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/TestUtility.cs new file mode 100644 index 00000000..03275848 --- /dev/null +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/TestUtility.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Mono.Cecil; + +namespace Microsoft.NET.Sdk.Functions.Test +{ + public static class TestUtility + { + public static MethodDefinition GetMethodDefinition(Type type, string methodName) + { + return GetTypeDefinition(type).Methods.SingleOrDefault(p => p.Name == methodName); + } + + public static TypeDefinition GetTypeDefinition(Type type) + { + var module = ModuleDefinition.ReadModule(type.Assembly.Location); + return module.GetType(type.FullName.Replace("+", "/")); + } + + public static IEnumerable GetCustomAttributes(Type type, string methodName, string parameterName) + { + var methodDef = GetMethodDefinition(type, methodName); + var paramDef = methodDef.Parameters.SingleOrDefault(p => p.Name == parameterName); + return paramDef.CustomAttributes; + } + } +} diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/packages.config b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/packages.config index 72186cf7..903cb493 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/packages.config +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/packages.config @@ -28,6 +28,7 @@ + From 96b7d8e0c23e1df86cc74bb5b1fd7a83bd5f3e93 Mon Sep 17 00:00:00 2001 From: Fabio Cavalcante Date: Mon, 25 Nov 2019 17:40:03 -0800 Subject: [PATCH 04/27] Updating version to beta4 --- .../Targets/Microsoft.NET.Sdk.Functions.Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props index c1495cd5..e2ae9376 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.0.30-beta3 + 1.0.30-beta4 From ea779818b0394c14ca063f9f5cea4b5dddd70ae5 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Wed, 11 Dec 2019 07:52:05 -0800 Subject: [PATCH 05/27] fixing publish for v2 apps with .NET Core 3 SDK installed --- ...icrosoft.NET.Sdk.Functions.Publish.targets | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Publish.targets b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Publish.targets index 06dd98f5..b7935561 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Publish.targets +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Publish.targets @@ -167,5 +167,25 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and UserProvidedFunctionJsonFiles="@(UserProvidedFunctionJsonFiles)" FunctionsInDependencies="$(FunctionsInDependencies)" /> - - + + + + + + <_ResolvedCopyLocalPublishAssets> + bin\%(_ResolvedCopyLocalPublishAssets.DestinationSubDirectory) + + + + + + \ No newline at end of file From 472cdb80b6884aa298e03f5c677c0bf7feae276b Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Wed, 11 Dec 2019 10:45:54 -0800 Subject: [PATCH 06/27] bumping version to 1.0.30 --- .../Targets/Microsoft.NET.Sdk.Functions.Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props index e2ae9376..51da9345 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.0.30-beta4 + 1.0.30 From e045da6ff4daa3a02f6e45af1ac8b7f4fd9b97a8 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Thu, 12 Dec 2019 10:53:12 -0800 Subject: [PATCH 07/27] base deps file copy on AssemblyName, not ProjectName --- .../Targets/Microsoft.NET.Sdk.Functions.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets index d6667dad..dcdfa733 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets @@ -84,7 +84,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and ============================================================ --> - + - + - + From 349faf70298ed406d03cef6d3adf51b6195d2bf3 Mon Sep 17 00:00:00 2001 From: Fabio Cavalcante Date: Tue, 11 Feb 2020 17:26:18 -0800 Subject: [PATCH 10/27] Updating SDK version --- .../Targets/Microsoft.NET.Sdk.Functions.Version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props index 54223648..a22218e2 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.0.31 + 1.0.32 From 4eea0fb20467dd8187d7ac4dfeb71d5730401f5c Mon Sep 17 00:00:00 2001 From: Fabio Cavalcante Date: Fri, 28 Feb 2020 17:37:23 -0800 Subject: [PATCH 11/27] Fixing file move operation on build --- .../Targets/Microsoft.NET.Sdk.Functions.Build.targets | 2 +- .../Targets/Microsoft.NET.Sdk.Functions.Publish.targets | 2 +- .../Targets/Microsoft.NET.Sdk.Functions.Version.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets index ec9a1feb..dcdfa733 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets @@ -84,7 +84,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and ============================================================ --> - + diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props index a22218e2..eb8d2433 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.0.32 + 1.0.33 From 3bdbccd0fcac2482553997c4db34f76b5ff963c6 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Thu, 5 Mar 2020 12:09:22 -0800 Subject: [PATCH 12/27] Bumping ExtensionsMetadataGenerator to 1.1.5 --- .../Targets/ExtensionsMetadataGeneratorVersion.props | 2 +- .../Targets/Microsoft.NET.Sdk.Functions.Version.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/ExtensionsMetadataGeneratorVersion.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/ExtensionsMetadataGeneratorVersion.props index eee30fd6..8e3fe876 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/ExtensionsMetadataGeneratorVersion.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/ExtensionsMetadataGeneratorVersion.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.1.4 + 1.1.5 diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props index eb8d2433..3e794a03 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Version.props @@ -11,7 +11,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and - 1.0.33 + 1.0.34 From e91c1f06d009aae841384f9d5aaaef17be03da64 Mon Sep 17 00:00:00 2001 From: Yogesh Jagadeesan Date: Thu, 12 Mar 2020 12:34:36 -0700 Subject: [PATCH 13/27] Fixing eventhub trigger scenario (#400) Added eventHub handling scenario in AttributeExtensions --- FunctionsSdk.sln | 7 ++ .../AttributeExtensions.cs | 8 ++ .../Properties/AssemblyInfo.cs | 2 + .../FunctionJsonConverterTests.cs | 22 +++--- .../FunctionJsonConverterTests.cs | 70 ++++++++++++++++++ ...ET.Sdk.Functions.Generator.V1.Tests.csproj | 42 +++++++++++ .../PublicKey.snk | Bin 0 -> 160 bytes 7 files changed, 142 insertions(+), 9 deletions(-) create mode 100644 test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/FunctionJsonConverterTests.cs create mode 100644 test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/Microsoft.NET.Sdk.Functions.Generator.V1.Tests.csproj create mode 100644 test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/PublicKey.snk diff --git a/FunctionsSdk.sln b/FunctionsSdk.sln index c5081692..8e38c8f8 100644 --- a/FunctionsSdk.sln +++ b/FunctionsSdk.sln @@ -27,6 +27,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Functions EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Functions.MSBuild", "src\Microsoft.NET.Sdk.Functions.MSBuild\Microsoft.NET.Sdk.Functions.MSBuild.csproj", "{1DB38EB5-DBA9-4678-BB99-2BCD1255DDBE}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Functions.Generator.V1.Tests", "test\Microsoft.NET.Sdk.Functions.Generator.V1.Tests\Microsoft.NET.Sdk.Functions.Generator.V1.Tests.csproj", "{102600C1-C3BB-4EBF-8513-3CBD72716C10}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -57,6 +59,10 @@ Global {1DB38EB5-DBA9-4678-BB99-2BCD1255DDBE}.Debug|Any CPU.Build.0 = Debug|Any CPU {1DB38EB5-DBA9-4678-BB99-2BCD1255DDBE}.Release|Any CPU.ActiveCfg = Release|Any CPU {1DB38EB5-DBA9-4678-BB99-2BCD1255DDBE}.Release|Any CPU.Build.0 = Release|Any CPU + {102600C1-C3BB-4EBF-8513-3CBD72716C10}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {102600C1-C3BB-4EBF-8513-3CBD72716C10}.Debug|Any CPU.Build.0 = Debug|Any CPU + {102600C1-C3BB-4EBF-8513-3CBD72716C10}.Release|Any CPU.ActiveCfg = Release|Any CPU + {102600C1-C3BB-4EBF-8513-3CBD72716C10}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -68,6 +74,7 @@ Global {14DF7D60-3AE6-4A5C-A2FF-BE49720DDFEC} = {14D6456E-2F9D-4483-A378-03701A6EB12D} {B80DA350-8A69-4CD2-9E60-01C51B5A8633} = {9B6D0171-3FFD-4892-B407-B633CA4E6712} {1DB38EB5-DBA9-4678-BB99-2BCD1255DDBE} = {14D6456E-2F9D-4483-A378-03701A6EB12D} + {102600C1-C3BB-4EBF-8513-3CBD72716C10} = {9B6D0171-3FFD-4892-B407-B633CA4E6712} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DA731A15-774F-46C2-B8DF-298F828DCC2A} diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs b/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs index c937da11..97b35558 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/AttributeExtensions.cs @@ -202,6 +202,14 @@ private static string NormalizePropertyName(Attribute attribute, PropertyInfo pr return "path"; } } + else if (attributeName == "EventHubTriggerAttribute" && + attribute.GetType().Assembly.GetName().Version.Major == 2) + { + if (propertyName == "EventHubName") + { + return "path"; + } + } else if (attributeName == "ServiceBusTriggerAttribute") { if (propertyName == "Access") diff --git a/src/Microsoft.NET.Sdk.Functions.Generator/Properties/AssemblyInfo.cs b/src/Microsoft.NET.Sdk.Functions.Generator/Properties/AssemblyInfo.cs index e05fe286..e4d46f2d 100644 --- a/src/Microsoft.NET.Sdk.Functions.Generator/Properties/AssemblyInfo.cs +++ b/src/Microsoft.NET.Sdk.Functions.Generator/Properties/AssemblyInfo.cs @@ -2,8 +2,10 @@ #if RELESE_BUILD [assembly: InternalsVisibleTo("Microsoft.NET.Sdk.Functions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] +[assembly: InternalsVisibleTo("Microsoft.NET.Sdk.Functions.Test.V1, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d992c94d1071c5467ec320753f4a3cd4e3968a8d16944c09b12baeddee07f0c02c87552de2fe4bf62c35e95449f9a2b3a3930701029981b0dee4dbea68321a19a96289c05a4be24713e1447dd803be701582e6fe6ad53190f51c7200128eb663a81737f32cde60d114a1387e525618e16968c42efa9b3939e6e19806ca230ed1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] #else [assembly: InternalsVisibleTo("Microsoft.NET.Sdk.Functions.Test")] +[assembly: InternalsVisibleTo("Microsoft.NET.Sdk.Functions.Test.V1")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] #endif \ No newline at end of file diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs index 8d7828a7..7888174e 100644 --- a/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs +++ b/test/Microsoft.NET.Sdk.Functions.Generator.Tests/FunctionJsonConverterTests.cs @@ -41,15 +41,15 @@ public static void Run8() { } } [Theory] - [InlineData("MyHttpTrigger", "httpTrigger", "request")] - [InlineData("MyBlobTrigger", "blobTrigger", "blobContent")] - [InlineData("MyQueueTrigger", "queueTrigger", "queue")] - [InlineData("MyEventHubTrigger", "eventHubTrigger", "message")] - [InlineData("MyTimerTrigger", "timerTrigger", "timer")] - [InlineData("MyServiceBusTrigger", "serviceBusTrigger", "message")] - [InlineData("MyManualTrigger", "manualTrigger", "input")] - [InlineData("MyManualTriggerWithoutParameters", "manualTrigger", null)] - public void FunctionMethodsAreExported(string functionName, string type, string parameterName) + [InlineData("MyHttpTrigger", "httpTrigger", "request", "authLevel", "function")] + [InlineData("MyBlobTrigger", "blobTrigger", "blobContent", "path", "blob.txt")] + [InlineData("MyQueueTrigger", "queueTrigger", "queue", "queueName", "queue")] + [InlineData("MyTimerTrigger", "timerTrigger", "timer", "schedule", "00:30:00")] + [InlineData("MyServiceBusTrigger", "serviceBusTrigger", "message", "queueName", "queue")] + [InlineData("MyManualTrigger", "manualTrigger", "input", null, null)] + [InlineData("MyManualTriggerWithoutParameters", "manualTrigger", null, null, null)] + [InlineData("MyEventHubTrigger", "eventHubTrigger", "message", "eventHubName", "hub")] + public void FunctionMethodsAreExported(string functionName, string type, string parameterName, string bindingName, string bindingValue) { var logger = new RecorderLogger(); var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies: false); @@ -58,6 +58,10 @@ public void FunctionMethodsAreExported(string functionName, string type, string var binding = schema.Bindings.Single(); binding.Value("type").Should().Be(type); binding.Value("name").Should().Be(parameterName); + if (bindingName != null) + { + binding.Value(bindingName).Should().Be(bindingValue); + } logger.Errors.Should().BeEmpty(); logger.Warnings.Should().BeEmpty(); } diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/FunctionJsonConverterTests.cs b/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/FunctionJsonConverterTests.cs new file mode 100644 index 00000000..e86f8c90 --- /dev/null +++ b/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/FunctionJsonConverterTests.cs @@ -0,0 +1,70 @@ +using System; +using System.IO; +using System.Linq; +using System.Net.Http; +using FluentAssertions; +using MakeFunctionJson; +using Microsoft.Azure.WebJobs; +using Microsoft.Azure.WebJobs.ServiceBus; +using Microsoft.ServiceBus.Messaging; +using Microsoft.WindowsAzure.Storage.Queue; +using Xunit; + +namespace Microsoft.NET.Sdk.Functions.Test.V1 +{ + public class FunctionJsonConverterTests + { + public class FunctionsClass + { + [FunctionName("MyHttpTrigger")] + public static void Run1([HttpTrigger] HttpRequestMessage request) { } + + [FunctionName("MyBlobTrigger")] + public static void Run2([BlobTrigger("blob.txt")] string blobContent) { } + + [FunctionName("MyQueueTrigger")] + public static void Run3([QueueTrigger("queue")] CloudQueue queue) { } + + [FunctionName("MyEventHubTrigger")] + public static void Run4([EventHubTrigger("hub")] EventData message) { } + + [FunctionName("MyTimerTrigger")] + public static void Run5([TimerTrigger("00:30:00")] TimerInfo timer) { } + + [FunctionName("MyServiceBusTrigger")] + public static void Run6([ServiceBusTrigger("queue")] string message) { } + + [FunctionName("MyManualTrigger"), NoAutomaticTrigger] + public static void Run7(string input) { } + + [FunctionName("MyManualTriggerWithoutParameters"), NoAutomaticTrigger] + public static void Run8() { } + } + + [Theory] + [InlineData("MyHttpTrigger", "httpTrigger", "request", "authLevel", "function")] + [InlineData("MyBlobTrigger", "blobTrigger", "blobContent", "path", "blob.txt")] + [InlineData("MyQueueTrigger", "queueTrigger", "queue", "queueName", "queue")] + [InlineData("MyTimerTrigger", "timerTrigger", "timer", "schedule", "00:30:00")] + [InlineData("MyServiceBusTrigger", "serviceBusTrigger", "message", "accessRights", "manage")] + [InlineData("MyManualTrigger", "manualTrigger", "input", null, null)] + [InlineData("MyManualTriggerWithoutParameters", "manualTrigger", null, null, null)] + [InlineData("MyEventHubTrigger", "eventHubTrigger", "message", "path", "hub")] + public void FunctionMethodsAreExported(string functionName, string type, string parameterName, string bindingName, string bindingValue) + { + var logger = new RecorderLogger(); + var converter = new FunctionJsonConverter(logger, ".", ".", functionsInDependencies: false); + var functions = converter.GenerateFunctions(new[] { TestUtility.GetTypeDefinition(typeof(FunctionsClass)) }); + var schema = functions.Single(e => Path.GetFileName(e.Value.outputFile.DirectoryName) == functionName).Value.schema; + var binding = schema.Bindings.Single(); + binding.Value("type").Should().Be(type); + binding.Value("name").Should().Be(parameterName); + if(bindingName != null) + { + binding.Value(bindingName).Should().Be(bindingValue); + } + logger.Errors.Should().BeEmpty(); + logger.Warnings.Should().BeEmpty(); + } + } +} diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/Microsoft.NET.Sdk.Functions.Generator.V1.Tests.csproj b/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/Microsoft.NET.Sdk.Functions.Generator.V1.Tests.csproj new file mode 100644 index 00000000..d50c25a8 --- /dev/null +++ b/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/Microsoft.NET.Sdk.Functions.Generator.V1.Tests.csproj @@ -0,0 +1,42 @@ + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + PublicKey.snk + true + + + netcoreapp2.0 + false + + Library + + Microsoft.NET.Sdk.Functions.Test.V1 + Microsoft.NET.Sdk.Functions.Test.V1 + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + diff --git a/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/PublicKey.snk b/test/Microsoft.NET.Sdk.Functions.Generator.V1.Tests/PublicKey.snk new file mode 100644 index 0000000000000000000000000000000000000000..4bd8b5fdd8a2f7a8c9334cfba74529a6d2aa4788 GIT binary patch literal 160 zcmV;R0AK$ABme*efB*oL000060ssI2Bme+XQ$aBR1ONa50098mlF3aFam7Y{!yt7( zN<7r#mWquQluQY+E3Vz{2k^iwhgB`&{!8{OHR)7I`J%Ij<#c{7dP`P-eA!bp*Vh0Rv6)FXv8l1 On>jh=;g|->BM#AX@Igob literal 0 HcmV?d00001 From 8605bede07cc55b3419d81471678fd3f842c40b5 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Thu, 12 Mar 2020 14:00:19 -0700 Subject: [PATCH 14/27] adding _IsFunctionsSdkBuild to props file --- .../Targets/Microsoft.NET.Sdk.Functions.props | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.props b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.props index 1f3ba6dc..35e88a5b 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.props +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.props @@ -12,6 +12,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and <_NuGetPackageRoot>$([MSBuild]::EnsureTrailingSlash('$(NuGetPackageRoot)')) + <_IsFunctionsSdkBuild>true - - + + + + + + + - 1.0.35 + 1.0.36 From 4c5826245e7155db8a669c8a7a0ee102d27fe005 Mon Sep 17 00:00:00 2001 From: Brett Samblanet Date: Mon, 4 May 2020 13:17:41 -0700 Subject: [PATCH 18/27] running unit tests during ci --- build.fsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/build.fsx b/build.fsx index bb898ffb..1c576ae8 100644 --- a/build.fsx +++ b/build.fsx @@ -57,6 +57,18 @@ Target "Build" (fun _ -> Configuration = "Release"}) ) +Target "UnitTest" (fun _ -> + DotNetCli.Test (fun p -> + {p with + Project = "test\\Microsoft.NET.Sdk.Functions.Generator.Tests" + Configuration = "Debug"}) + + DotNetCli.Test (fun p -> + {p with + Project = "test\\Microsoft.NET.Sdk.Functions.MSBuild.Tests" + Configuration = "Debug"}) +) + Target "GenerateZipToSign" (fun _ -> !! (packOutputPath @@ "net46\\Microsoft.NET.Sdk.Functions.dll") ++ (buildTaskOutputPath @@ "net46\\Microsoft.NET.Sdk.Functions.MSBuild.dll") @@ -197,6 +209,7 @@ Target "Publish" (fun _ -> Dependencies "Clean" ==> "Build" + ==> "UnitTest" ==> "GenerateZipToSign" ==> "UploadZipToSign" ==> "EnqueueSignMessage" From 759a7eccb4489ddd97af2f1bc44b78c719f235c7 Mon Sep 17 00:00:00 2001 From: Vijay Ramakrishnan Date: Thu, 14 May 2020 21:35:02 -0700 Subject: [PATCH 19/27] Fixing the task assembly load issue --- Directory.Build.props | 9 +++++++++ .../Microsoft.NET.Sdk.Functions.Build.targets | 3 ++- .../Microsoft.NET.Sdk.Functions.Publish.targets | 3 ++- .../Microsoft.NET.Sdk.Functions.Version.props | 2 +- .../Tasks/GenerateFunctions.cs | 12 +++++++----- 5 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 Directory.Build.props diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..2b008035 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,9 @@ + + + + 2.0.0.0 + $(AssemblyVersion) + $(AssemblyVersion) + + + diff --git a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets index cb191679..01d6a78d 100644 --- a/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets +++ b/src/Microsoft.NET.Sdk.Functions.MSBuild/Targets/Microsoft.NET.Sdk.Functions.Build.targets @@ -47,7 +47,8 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and UseNETCoreGenerator="$(UseNETCoreGenerator)" UseNETFrameworkGenerator="$(UseNETFrameworkGenerator)" UserProvidedFunctionJsonFiles="@(UserProvidedFunctionJsonFiles)" - FunctionsInDependencies="$(FunctionsInDependencies)"/> + FunctionsInDependencies="$(FunctionsInDependencies)" + TaskAssemblyDirectory="$(_FunctionsTasksDir)"/> [ C ] + * ------ ---------- + * A Number + * B Number + * E Number + * H Number + * K Letter + * P Letter + * Q Letter + * S Letter + * + */ +$.validator.addMethod( "cifES", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + var cifRegEx = new RegExp( /^([ABCDEFGHJKLMNPQRSUVW])(\d{7})([0-9A-J])$/gi ); + var letter = value.substring( 0, 1 ), // [ T ] + number = value.substring( 1, 8 ), // [ P ][ P ][ N ][ N ][ N ][ N ][ N ] + control = value.substring( 8, 9 ), // [ C ] + all_sum = 0, + even_sum = 0, + odd_sum = 0, + i, n, + control_digit, + control_letter; + + function isOdd( n ) { + return n % 2 === 0; + } + + // Quick format test + if ( value.length !== 9 || !cifRegEx.test( value ) ) { + return false; + } + + for ( i = 0; i < number.length; i++ ) { + n = parseInt( number[ i ], 10 ); + + // Odd positions + if ( isOdd( i ) ) { + + // Odd positions are multiplied first. + n *= 2; + + // If the multiplication is bigger than 10 we need to adjust + odd_sum += n < 10 ? n : n - 9; + + // Even positions + // Just sum them + } else { + even_sum += n; + } + } + + all_sum = even_sum + odd_sum; + control_digit = ( 10 - ( all_sum ).toString().substr( -1 ) ).toString(); + control_digit = parseInt( control_digit, 10 ) > 9 ? "0" : control_digit; + control_letter = "JABCDEFGHI".substr( control_digit, 1 ).toString(); + + // Control must be a digit + if ( letter.match( /[ABEH]/ ) ) { + return control === control_digit; + + // Control must be a letter + } else if ( letter.match( /[KPQS]/ ) ) { + return control === control_letter; + } + + // Can be either + return control === control_digit || control === control_letter; + +}, "Please specify a valid CIF number." ); + +/* + * Brazillian CPF number (Cadastrado de Pessoas Físicas) is the equivalent of a Brazilian tax registration number. + * CPF numbers have 11 digits in total: 9 numbers followed by 2 check numbers that are being used for validation. + */ +$.validator.addMethod( "cpfBR", function( value ) { + + // Removing special characters from value + value = value.replace( /([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g, "" ); + + // Checking value to have 11 digits only + if ( value.length !== 11 ) { + return false; + } + + var sum = 0, + firstCN, secondCN, checkResult, i; + + firstCN = parseInt( value.substring( 9, 10 ), 10 ); + secondCN = parseInt( value.substring( 10, 11 ), 10 ); + + checkResult = function( sum, cn ) { + var result = ( sum * 10 ) % 11; + if ( ( result === 10 ) || ( result === 11 ) ) { + result = 0; + } + return ( result === cn ); + }; + + // Checking for dump data + if ( value === "" || + value === "00000000000" || + value === "11111111111" || + value === "22222222222" || + value === "33333333333" || + value === "44444444444" || + value === "55555555555" || + value === "66666666666" || + value === "77777777777" || + value === "88888888888" || + value === "99999999999" + ) { + return false; + } + + // Step 1 - using first Check Number: + for ( i = 1; i <= 9; i++ ) { + sum = sum + parseInt( value.substring( i - 1, i ), 10 ) * ( 11 - i ); + } + + // If first Check Number (CN) is valid, move to Step 2 - using second Check Number: + if ( checkResult( sum, firstCN ) ) { + sum = 0; + for ( i = 1; i <= 10; i++ ) { + sum = sum + parseInt( value.substring( i - 1, i ), 10 ) * ( 12 - i ); + } + return checkResult( sum, secondCN ); + } + return false; + +}, "Please specify a valid CPF number" ); + +// https://jqueryvalidation.org/creditcard-method/ +// based on https://en.wikipedia.org/wiki/Luhn_algorithm +$.validator.addMethod( "creditcard", function( value, element ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + + // Accept only spaces, digits and dashes + if ( /[^0-9 \-]+/.test( value ) ) { + return false; + } + + var nCheck = 0, + nDigit = 0, + bEven = false, + n, cDigit; + + value = value.replace( /\D/g, "" ); + + // Basing min and max length on + // https://developer.ean.com/general_info/Valid_Credit_Card_Types + if ( value.length < 13 || value.length > 19 ) { + return false; + } + + for ( n = value.length - 1; n >= 0; n-- ) { + cDigit = value.charAt( n ); + nDigit = parseInt( cDigit, 10 ); + if ( bEven ) { + if ( ( nDigit *= 2 ) > 9 ) { + nDigit -= 9; + } + } + + nCheck += nDigit; + bEven = !bEven; + } + + return ( nCheck % 10 ) === 0; +}, "Please enter a valid credit card number." ); + +/* NOTICE: Modified version of Castle.Components.Validator.CreditCardValidator + * Redistributed under the the Apache License 2.0 at http://www.apache.org/licenses/LICENSE-2.0 + * Valid Types: mastercard, visa, amex, dinersclub, enroute, discover, jcb, unknown, all (overrides all other settings) + */ +$.validator.addMethod( "creditcardtypes", function( value, element, param ) { + if ( /[^0-9\-]+/.test( value ) ) { + return false; + } + + value = value.replace( /\D/g, "" ); + + var validTypes = 0x0000; + + if ( param.mastercard ) { + validTypes |= 0x0001; + } + if ( param.visa ) { + validTypes |= 0x0002; + } + if ( param.amex ) { + validTypes |= 0x0004; + } + if ( param.dinersclub ) { + validTypes |= 0x0008; + } + if ( param.enroute ) { + validTypes |= 0x0010; + } + if ( param.discover ) { + validTypes |= 0x0020; + } + if ( param.jcb ) { + validTypes |= 0x0040; + } + if ( param.unknown ) { + validTypes |= 0x0080; + } + if ( param.all ) { + validTypes = 0x0001 | 0x0002 | 0x0004 | 0x0008 | 0x0010 | 0x0020 | 0x0040 | 0x0080; + } + if ( validTypes & 0x0001 && /^(5[12345])/.test( value ) ) { // Mastercard + return value.length === 16; + } + if ( validTypes & 0x0002 && /^(4)/.test( value ) ) { // Visa + return value.length === 16; + } + if ( validTypes & 0x0004 && /^(3[47])/.test( value ) ) { // Amex + return value.length === 15; + } + if ( validTypes & 0x0008 && /^(3(0[012345]|[68]))/.test( value ) ) { // Dinersclub + return value.length === 14; + } + if ( validTypes & 0x0010 && /^(2(014|149))/.test( value ) ) { // Enroute + return value.length === 15; + } + if ( validTypes & 0x0020 && /^(6011)/.test( value ) ) { // Discover + return value.length === 16; + } + if ( validTypes & 0x0040 && /^(3)/.test( value ) ) { // Jcb + return value.length === 16; + } + if ( validTypes & 0x0040 && /^(2131|1800)/.test( value ) ) { // Jcb + return value.length === 15; + } + if ( validTypes & 0x0080 ) { // Unknown + return true; + } + return false; +}, "Please enter a valid credit card number." ); + +/** + * Validates currencies with any given symbols by @jameslouiz + * Symbols can be optional or required. Symbols required by default + * + * Usage examples: + * currency: ["£", false] - Use false for soft currency validation + * currency: ["$", false] + * currency: ["RM", false] - also works with text based symbols such as "RM" - Malaysia Ringgit etc + * + * + * + * Soft symbol checking + * currencyInput: { + * currency: ["$", false] + * } + * + * Strict symbol checking (default) + * currencyInput: { + * currency: "$" + * //OR + * currency: ["$", true] + * } + * + * Multiple Symbols + * currencyInput: { + * currency: "$,£,¢" + * } + */ +$.validator.addMethod( "currency", function( value, element, param ) { + var isParamString = typeof param === "string", + symbol = isParamString ? param : param[ 0 ], + soft = isParamString ? true : param[ 1 ], + regex; + + symbol = symbol.replace( /,/g, "" ); + symbol = soft ? symbol + "]" : symbol + "]?"; + regex = "^[" + symbol + "([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$"; + regex = new RegExp( regex ); + return this.optional( element ) || regex.test( value ); + +}, "Please specify a valid currency" ); + +$.validator.addMethod( "dateFA", function( value, element ) { + return this.optional( element ) || /^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test( value ); +}, $.validator.messages.date ); + +/** + * Return true, if the value is a valid date, also making this formal check dd/mm/yyyy. + * + * @example $.validator.methods.date("01/01/1900") + * @result true + * + * @example $.validator.methods.date("01/13/1990") + * @result false + * + * @example $.validator.methods.date("01.01.1900") + * @result false + * + * @example + * @desc Declares an optional input element whose value must be a valid date. + * + * @name $.validator.methods.dateITA + * @type Boolean + * @cat Plugins/Validate/Methods + */ +$.validator.addMethod( "dateITA", function( value, element ) { + var check = false, + re = /^\d{1,2}\/\d{1,2}\/\d{4}$/, + adata, gg, mm, aaaa, xdata; + if ( re.test( value ) ) { + adata = value.split( "/" ); + gg = parseInt( adata[ 0 ], 10 ); + mm = parseInt( adata[ 1 ], 10 ); + aaaa = parseInt( adata[ 2 ], 10 ); + xdata = new Date( Date.UTC( aaaa, mm - 1, gg, 12, 0, 0, 0 ) ); + if ( ( xdata.getUTCFullYear() === aaaa ) && ( xdata.getUTCMonth() === mm - 1 ) && ( xdata.getUTCDate() === gg ) ) { + check = true; + } else { + check = false; + } + } else { + check = false; + } + return this.optional( element ) || check; +}, $.validator.messages.date ); + +$.validator.addMethod( "dateNL", function( value, element ) { + return this.optional( element ) || /^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test( value ); +}, $.validator.messages.date ); + +// Older "accept" file extension method. Old docs: http://docs.jquery.com/Plugins/Validation/Methods/accept +$.validator.addMethod( "extension", function( value, element, param ) { + param = typeof param === "string" ? param.replace( /,/g, "|" ) : "png|jpe?g|gif"; + return this.optional( element ) || value.match( new RegExp( "\\.(" + param + ")$", "i" ) ); +}, $.validator.format( "Please enter a value with a valid extension." ) ); + +/** + * Dutch giro account numbers (not bank numbers) have max 7 digits + */ +$.validator.addMethod( "giroaccountNL", function( value, element ) { + return this.optional( element ) || /^[0-9]{1,7}$/.test( value ); +}, "Please specify a valid giro account number" ); + +/** + * IBAN is the international bank account number. + * It has a country - specific format, that is checked here too + * + * Validation is case-insensitive. Please make sure to normalize input yourself. + */ +$.validator.addMethod( "iban", function( value, element ) { + + // Some quick simple tests to prevent needless work + if ( this.optional( element ) ) { + return true; + } + + // Remove spaces and to upper case + var iban = value.replace( / /g, "" ).toUpperCase(), + ibancheckdigits = "", + leadingZeroes = true, + cRest = "", + cOperator = "", + countrycode, ibancheck, charAt, cChar, bbanpattern, bbancountrypatterns, ibanregexp, i, p; + + // Check for IBAN code length. + // It contains: + // country code ISO 3166-1 - two letters, + // two check digits, + // Basic Bank Account Number (BBAN) - up to 30 chars + var minimalIBANlength = 5; + if ( iban.length < minimalIBANlength ) { + return false; + } + + // Check the country code and find the country specific format + countrycode = iban.substring( 0, 2 ); + bbancountrypatterns = { + "AL": "\\d{8}[\\dA-Z]{16}", + "AD": "\\d{8}[\\dA-Z]{12}", + "AT": "\\d{16}", + "AZ": "[\\dA-Z]{4}\\d{20}", + "BE": "\\d{12}", + "BH": "[A-Z]{4}[\\dA-Z]{14}", + "BA": "\\d{16}", + "BR": "\\d{23}[A-Z][\\dA-Z]", + "BG": "[A-Z]{4}\\d{6}[\\dA-Z]{8}", + "CR": "\\d{17}", + "HR": "\\d{17}", + "CY": "\\d{8}[\\dA-Z]{16}", + "CZ": "\\d{20}", + "DK": "\\d{14}", + "DO": "[A-Z]{4}\\d{20}", + "EE": "\\d{16}", + "FO": "\\d{14}", + "FI": "\\d{14}", + "FR": "\\d{10}[\\dA-Z]{11}\\d{2}", + "GE": "[\\dA-Z]{2}\\d{16}", + "DE": "\\d{18}", + "GI": "[A-Z]{4}[\\dA-Z]{15}", + "GR": "\\d{7}[\\dA-Z]{16}", + "GL": "\\d{14}", + "GT": "[\\dA-Z]{4}[\\dA-Z]{20}", + "HU": "\\d{24}", + "IS": "\\d{22}", + "IE": "[\\dA-Z]{4}\\d{14}", + "IL": "\\d{19}", + "IT": "[A-Z]\\d{10}[\\dA-Z]{12}", + "KZ": "\\d{3}[\\dA-Z]{13}", + "KW": "[A-Z]{4}[\\dA-Z]{22}", + "LV": "[A-Z]{4}[\\dA-Z]{13}", + "LB": "\\d{4}[\\dA-Z]{20}", + "LI": "\\d{5}[\\dA-Z]{12}", + "LT": "\\d{16}", + "LU": "\\d{3}[\\dA-Z]{13}", + "MK": "\\d{3}[\\dA-Z]{10}\\d{2}", + "MT": "[A-Z]{4}\\d{5}[\\dA-Z]{18}", + "MR": "\\d{23}", + "MU": "[A-Z]{4}\\d{19}[A-Z]{3}", + "MC": "\\d{10}[\\dA-Z]{11}\\d{2}", + "MD": "[\\dA-Z]{2}\\d{18}", + "ME": "\\d{18}", + "NL": "[A-Z]{4}\\d{10}", + "NO": "\\d{11}", + "PK": "[\\dA-Z]{4}\\d{16}", + "PS": "[\\dA-Z]{4}\\d{21}", + "PL": "\\d{24}", + "PT": "\\d{21}", + "RO": "[A-Z]{4}[\\dA-Z]{16}", + "SM": "[A-Z]\\d{10}[\\dA-Z]{12}", + "SA": "\\d{2}[\\dA-Z]{18}", + "RS": "\\d{18}", + "SK": "\\d{20}", + "SI": "\\d{15}", + "ES": "\\d{20}", + "SE": "\\d{20}", + "CH": "\\d{5}[\\dA-Z]{12}", + "TN": "\\d{20}", + "TR": "\\d{5}[\\dA-Z]{17}", + "AE": "\\d{3}\\d{16}", + "GB": "[A-Z]{4}\\d{14}", + "VG": "[\\dA-Z]{4}\\d{16}" + }; + + bbanpattern = bbancountrypatterns[ countrycode ]; + + // As new countries will start using IBAN in the + // future, we only check if the countrycode is known. + // This prevents false negatives, while almost all + // false positives introduced by this, will be caught + // by the checksum validation below anyway. + // Strict checking should return FALSE for unknown + // countries. + if ( typeof bbanpattern !== "undefined" ) { + ibanregexp = new RegExp( "^[A-Z]{2}\\d{2}" + bbanpattern + "$", "" ); + if ( !( ibanregexp.test( iban ) ) ) { + return false; // Invalid country specific format + } + } + + // Now check the checksum, first convert to digits + ibancheck = iban.substring( 4, iban.length ) + iban.substring( 0, 4 ); + for ( i = 0; i < ibancheck.length; i++ ) { + charAt = ibancheck.charAt( i ); + if ( charAt !== "0" ) { + leadingZeroes = false; + } + if ( !leadingZeroes ) { + ibancheckdigits += "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ".indexOf( charAt ); + } + } + + // Calculate the result of: ibancheckdigits % 97 + for ( p = 0; p < ibancheckdigits.length; p++ ) { + cChar = ibancheckdigits.charAt( p ); + cOperator = "" + cRest + "" + cChar; + cRest = cOperator % 97; + } + return cRest === 1; +}, "Please specify a valid IBAN" ); + +$.validator.addMethod( "integer", function( value, element ) { + return this.optional( element ) || /^-?\d+$/.test( value ); +}, "A positive or negative non-decimal number please" ); + +$.validator.addMethod( "ipv4", function( value, element ) { + return this.optional( element ) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test( value ); +}, "Please enter a valid IP v4 address." ); + +$.validator.addMethod( "ipv6", function( value, element ) { + return this.optional( element ) || /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i.test( value ); +}, "Please enter a valid IP v6 address." ); + +$.validator.addMethod( "lettersonly", function( value, element ) { + return this.optional( element ) || /^[a-z]+$/i.test( value ); +}, "Letters only please" ); + +$.validator.addMethod( "letterswithbasicpunc", function( value, element ) { + return this.optional( element ) || /^[a-z\-.,()'"\s]+$/i.test( value ); +}, "Letters or punctuation only please" ); + +$.validator.addMethod( "mobileNL", function( value, element ) { + return this.optional( element ) || /^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)6((\s|\s?\-\s?)?[0-9]){8}$/.test( value ); +}, "Please specify a valid mobile number" ); + +/* For UK phone functions, do the following server side processing: + * Compare original input with this RegEx pattern: + * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ + * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' + * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. + * A number of very detailed GB telephone number RegEx patterns can also be found at: + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + */ +$.validator.addMethod( "mobileUK", function( phone_number, element ) { + phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/ ); +}, "Please specify a valid mobile number" ); + +$.validator.addMethod( "netmask", function( value, element ) { + return this.optional( element ) || /^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test( value ); +}, "Please enter a valid netmask." ); + +/* + * The NIE (Número de Identificación de Extranjero) is a Spanish tax identification number assigned by the Spanish + * authorities to any foreigner. + * + * The NIE is the equivalent of a Spaniards Número de Identificación Fiscal (NIF) which serves as a fiscal + * identification number. The CIF number (Certificado de Identificación Fiscal) is equivalent to the NIF, but applies to + * companies rather than individuals. The NIE consists of an 'X' or 'Y' followed by 7 or 8 digits then another letter. + */ +$.validator.addMethod( "nieES", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + var nieRegEx = new RegExp( /^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi ); + var validChars = "TRWAGMYFPDXBNJZSQVHLCKET", + letter = value.substr( value.length - 1 ).toUpperCase(), + number; + + value = value.toString().toUpperCase(); + + // Quick format test + if ( value.length > 10 || value.length < 9 || !nieRegEx.test( value ) ) { + return false; + } + + // X means same number + // Y means number + 10000000 + // Z means number + 20000000 + value = value.replace( /^[X]/, "0" ) + .replace( /^[Y]/, "1" ) + .replace( /^[Z]/, "2" ); + + number = value.length === 9 ? value.substr( 0, 8 ) : value.substr( 0, 9 ); + + return validChars.charAt( parseInt( number, 10 ) % 23 ) === letter; + +}, "Please specify a valid NIE number." ); + +/* + * The Número de Identificación Fiscal ( NIF ) is the way tax identification used in Spain for individuals + */ +$.validator.addMethod( "nifES", function( value, element ) { + "use strict"; + + if ( this.optional( element ) ) { + return true; + } + + value = value.toUpperCase(); + + // Basic format test + if ( !value.match( "((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)" ) ) { + return false; + } + + // Test NIF + if ( /^[0-9]{8}[A-Z]{1}$/.test( value ) ) { + return ( "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 0 ) % 23 ) === value.charAt( 8 ) ); + } + + // Test specials NIF (starts with K, L or M) + if ( /^[KLM]{1}/.test( value ) ) { + return ( value[ 8 ] === "TRWAGMYFPDXBNJZSQVHLCKE".charAt( value.substring( 8, 1 ) % 23 ) ); + } + + return false; + +}, "Please specify a valid NIF number." ); + +/* + * Numer identyfikacji podatkowej ( NIP ) is the way tax identification used in Poland for companies + */ +$.validator.addMethod( "nipPL", function( value ) { + "use strict"; + + value = value.replace( /[^0-9]/g, "" ); + + if ( value.length !== 10 ) { + return false; + } + + var arrSteps = [ 6, 5, 7, 2, 3, 4, 5, 6, 7 ]; + var intSum = 0; + for ( var i = 0; i < 9; i++ ) { + intSum += arrSteps[ i ] * value[ i ]; + } + var int2 = intSum % 11; + var intControlNr = ( int2 === 10 ) ? 0 : int2; + + return ( intControlNr === parseInt( value[ 9 ], 10 ) ); +}, "Please specify a valid NIP number." ); + +$.validator.addMethod( "notEqualTo", function( value, element, param ) { + return this.optional( element ) || !$.validator.methods.equalTo.call( this, value, element, param ); +}, "Please enter a different value, values must not be the same." ); + +$.validator.addMethod( "nowhitespace", function( value, element ) { + return this.optional( element ) || /^\S+$/i.test( value ); +}, "No white space please" ); + +/** +* Return true if the field value matches the given format RegExp +* +* @example $.validator.methods.pattern("AR1004",element,/^AR\d{4}$/) +* @result true +* +* @example $.validator.methods.pattern("BR1004",element,/^AR\d{4}$/) +* @result false +* +* @name $.validator.methods.pattern +* @type Boolean +* @cat Plugins/Validate/Methods +*/ +$.validator.addMethod( "pattern", function( value, element, param ) { + if ( this.optional( element ) ) { + return true; + } + if ( typeof param === "string" ) { + param = new RegExp( "^(?:" + param + ")$" ); + } + return param.test( value ); +}, "Invalid format." ); + +/** + * Dutch phone numbers have 10 digits (or 11 and start with +31). + */ +$.validator.addMethod( "phoneNL", function( value, element ) { + return this.optional( element ) || /^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test( value ); +}, "Please specify a valid phone number." ); + +/* For UK phone functions, do the following server side processing: + * Compare original input with this RegEx pattern: + * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ + * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' + * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. + * A number of very detailed GB telephone number RegEx patterns can also be found at: + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + */ + +// Matches UK landline + mobile, accepting only 01-3 for landline or 07 for mobile to exclude many premium numbers +$.validator.addMethod( "phonesUK", function( phone_number, element ) { + phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/ ); +}, "Please specify a valid uk phone number" ); + +/* For UK phone functions, do the following server side processing: + * Compare original input with this RegEx pattern: + * ^\(?(?:(?:00\)?[\s\-]?\(?|\+)(44)\)?[\s\-]?\(?(?:0\)?[\s\-]?\(?)?|0)([1-9]\d{1,4}\)?[\s\d\-]+)$ + * Extract $1 and set $prefix to '+44' if $1 is '44', otherwise set $prefix to '0' + * Extract $2 and remove hyphens, spaces and parentheses. Phone number is combined $prefix and $2. + * A number of very detailed GB telephone number RegEx patterns can also be found at: + * http://www.aa-asterisk.org.uk/index.php/Regular_Expressions_for_Validating_and_Formatting_GB_Telephone_Numbers + */ +$.validator.addMethod( "phoneUK", function( phone_number, element ) { + phone_number = phone_number.replace( /\(|\)|\s+|-/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/ ); +}, "Please specify a valid phone number" ); + +/** + * Matches US phone number format + * + * where the area code may not start with 1 and the prefix may not start with 1 + * allows '-' or ' ' as a separator and allows parens around area code + * some people may want to put a '1' in front of their number + * + * 1(212)-999-2345 or + * 212 999 2344 or + * 212-999-0983 + * + * but not + * 111-123-5434 + * and not + * 212 123 4567 + */ +$.validator.addMethod( "phoneUS", function( phone_number, element ) { + phone_number = phone_number.replace( /\s+/g, "" ); + return this.optional( element ) || phone_number.length > 9 && + phone_number.match( /^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/ ); +}, "Please specify a valid phone number" ); + +/* +* Valida CEPs do brasileiros: +* +* Formatos aceitos: +* 99999-999 +* 99.999-999 +* 99999999 +*/ +$.validator.addMethod( "postalcodeBR", function( cep_value, element ) { + return this.optional( element ) || /^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test( cep_value ); +}, "Informe um CEP válido." ); + +/** + * Matches a valid Canadian Postal Code + * + * @example jQuery.validator.methods.postalCodeCA( "H0H 0H0", element ) + * @result true + * + * @example jQuery.validator.methods.postalCodeCA( "H0H0H0", element ) + * @result false + * + * @name jQuery.validator.methods.postalCodeCA + * @type Boolean + * @cat Plugins/Validate/Methods + */ +$.validator.addMethod( "postalCodeCA", function( value, element ) { + return this.optional( element ) || /^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test( value ); +}, "Please specify a valid postal code" ); + +/* Matches Italian postcode (CAP) */ +$.validator.addMethod( "postalcodeIT", function( value, element ) { + return this.optional( element ) || /^\d{5}$/.test( value ); +}, "Please specify a valid postal code" ); + +$.validator.addMethod( "postalcodeNL", function( value, element ) { + return this.optional( element ) || /^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test( value ); +}, "Please specify a valid postal code" ); + +// Matches UK postcode. Does not match to UK Channel Islands that have their own postcodes (non standard UK) +$.validator.addMethod( "postcodeUK", function( value, element ) { + return this.optional( element ) || /^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test( value ); +}, "Please specify a valid UK postcode" ); + +/* + * Lets you say "at least X inputs that match selector Y must be filled." + * + * The end result is that neither of these inputs: + * + * + * + * + * ...will validate unless at least one of them is filled. + * + * partnumber: {require_from_group: [1,".productinfo"]}, + * description: {require_from_group: [1,".productinfo"]} + * + * options[0]: number of fields that must be filled in the group + * options[1]: CSS selector that defines the group of conditionally required fields + */ +$.validator.addMethod( "require_from_group", function( value, element, options ) { + var $fields = $( options[ 1 ], element.form ), + $fieldsFirst = $fields.eq( 0 ), + validator = $fieldsFirst.data( "valid_req_grp" ) ? $fieldsFirst.data( "valid_req_grp" ) : $.extend( {}, this ), + isValid = $fields.filter( function() { + return validator.elementValue( this ); + } ).length >= options[ 0 ]; + + // Store the cloned validator for future validation + $fieldsFirst.data( "valid_req_grp", validator ); + + // If element isn't being validated, run each require_from_group field's validation rules + if ( !$( element ).data( "being_validated" ) ) { + $fields.data( "being_validated", true ); + $fields.each( function() { + validator.element( this ); + } ); + $fields.data( "being_validated", false ); + } + return isValid; +}, $.validator.format( "Please fill at least {0} of these fields." ) ); + +/* + * Lets you say "either at least X inputs that match selector Y must be filled, + * OR they must all be skipped (left blank)." + * + * The end result, is that none of these inputs: + * + * + * + * + * + * ...will validate unless either at least two of them are filled, + * OR none of them are. + * + * partnumber: {skip_or_fill_minimum: [2,".productinfo"]}, + * description: {skip_or_fill_minimum: [2,".productinfo"]}, + * color: {skip_or_fill_minimum: [2,".productinfo"]} + * + * options[0]: number of fields that must be filled in the group + * options[1]: CSS selector that defines the group of conditionally required fields + * + */ +$.validator.addMethod( "skip_or_fill_minimum", function( value, element, options ) { + var $fields = $( options[ 1 ], element.form ), + $fieldsFirst = $fields.eq( 0 ), + validator = $fieldsFirst.data( "valid_skip" ) ? $fieldsFirst.data( "valid_skip" ) : $.extend( {}, this ), + numberFilled = $fields.filter( function() { + return validator.elementValue( this ); + } ).length, + isValid = numberFilled === 0 || numberFilled >= options[ 0 ]; + + // Store the cloned validator for future validation + $fieldsFirst.data( "valid_skip", validator ); + + // If element isn't being validated, run each skip_or_fill_minimum field's validation rules + if ( !$( element ).data( "being_validated" ) ) { + $fields.data( "being_validated", true ); + $fields.each( function() { + validator.element( this ); + } ); + $fields.data( "being_validated", false ); + } + return isValid; +}, $.validator.format( "Please either skip these fields or fill at least {0} of them." ) ); + +/* Validates US States and/or Territories by @jdforsythe + * Can be case insensitive or require capitalization - default is case insensitive + * Can include US Territories or not - default does not + * Can include US Military postal abbreviations (AA, AE, AP) - default does not + * + * Note: "States" always includes DC (District of Colombia) + * + * Usage examples: + * + * This is the default - case insensitive, no territories, no military zones + * stateInput: { + * caseSensitive: false, + * includeTerritories: false, + * includeMilitary: false + * } + * + * Only allow capital letters, no territories, no military zones + * stateInput: { + * caseSensitive: false + * } + * + * Case insensitive, include territories but not military zones + * stateInput: { + * includeTerritories: true + * } + * + * Only allow capital letters, include territories and military zones + * stateInput: { + * caseSensitive: true, + * includeTerritories: true, + * includeMilitary: true + * } + * + */ +$.validator.addMethod( "stateUS", function( value, element, options ) { + var isDefault = typeof options === "undefined", + caseSensitive = ( isDefault || typeof options.caseSensitive === "undefined" ) ? false : options.caseSensitive, + includeTerritories = ( isDefault || typeof options.includeTerritories === "undefined" ) ? false : options.includeTerritories, + includeMilitary = ( isDefault || typeof options.includeMilitary === "undefined" ) ? false : options.includeMilitary, + regex; + + if ( !includeTerritories && !includeMilitary ) { + regex = "^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$"; + } else if ( includeTerritories && includeMilitary ) { + regex = "^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$"; + } else if ( includeTerritories ) { + regex = "^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$"; + } else { + regex = "^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$"; + } + + regex = caseSensitive ? new RegExp( regex ) : new RegExp( regex, "i" ); + return this.optional( element ) || regex.test( value ); +}, "Please specify a valid state" ); + +// TODO check if value starts with <, otherwise don't try stripping anything +$.validator.addMethod( "strippedminlength", function( value, element, param ) { + return $( value ).text().length >= param; +}, $.validator.format( "Please enter at least {0} characters" ) ); + +$.validator.addMethod( "time", function( value, element ) { + return this.optional( element ) || /^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test( value ); +}, "Please enter a valid time, between 00:00 and 23:59" ); + +$.validator.addMethod( "time12h", function( value, element ) { + return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value ); +}, "Please enter a valid time in 12-hour am/pm format" ); + +// Same as url, but TLD is optional +$.validator.addMethod( "url2", function( value, element ) { + return this.optional( element ) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test( value ); +}, $.validator.messages.url ); + +/** + * Return true, if the value is a valid vehicle identification number (VIN). + * + * Works with all kind of text inputs. + * + * @example + * @desc Declares a required input element whose value must be a valid vehicle identification number. + * + * @name $.validator.methods.vinUS + * @type Boolean + * @cat Plugins/Validate/Methods + */ +$.validator.addMethod( "vinUS", function( v ) { + if ( v.length !== 17 ) { + return false; + } + + var LL = [ "A", "B", "C", "D", "E", "F", "G", "H", "J", "K", "L", "M", "N", "P", "R", "S", "T", "U", "V", "W", "X", "Y", "Z" ], + VL = [ 1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 7, 9, 2, 3, 4, 5, 6, 7, 8, 9 ], + FL = [ 8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2 ], + rs = 0, + i, n, d, f, cd, cdv; + + for ( i = 0; i < 17; i++ ) { + f = FL[ i ]; + d = v.slice( i, i + 1 ); + if ( i === 8 ) { + cdv = d; + } + if ( !isNaN( d ) ) { + d *= f; + } else { + for ( n = 0; n < LL.length; n++ ) { + if ( d.toUpperCase() === LL[ n ] ) { + d = VL[ n ]; + d *= f; + if ( isNaN( cdv ) && n === 8 ) { + cdv = LL[ n ]; + } + break; + } + } + } + rs += d; + } + cd = rs % 11; + if ( cd === 10 ) { + cd = "X"; + } + if ( cd === cdv ) { + return true; + } + return false; +}, "The specified vehicle identification number (VIN) is invalid." ); + +$.validator.addMethod( "zipcodeUS", function( value, element ) { + return this.optional( element ) || /^\d{5}(-\d{4})?$/.test( value ); +}, "The specified US ZIP Code is invalid" ); + +$.validator.addMethod( "ziprange", function( value, element ) { + return this.optional( element ) || /^90[2-5]\d\{2\}-\d{4}$/.test( value ); +}, "Your ZIP-code must be in the range 902xx-xxxx to 905xx-xxxx" ); +return $; +})); \ No newline at end of file diff --git a/FunctionsSdkE2ETests/Razor/Mvc/wwwroot/lib/jquery-validation/dist/additional-methods.min.js b/FunctionsSdkE2ETests/Razor/Mvc/wwwroot/lib/jquery-validation/dist/additional-methods.min.js new file mode 100644 index 00000000..6767f24f --- /dev/null +++ b/FunctionsSdkE2ETests/Razor/Mvc/wwwroot/lib/jquery-validation/dist/additional-methods.min.js @@ -0,0 +1,4 @@ +/*! jQuery Validation Plugin - v1.17.0 - 7/29/2017 + * https://jqueryvalidation.org/ + * Copyright (c) 2017 Jörn Zaefferer; Licensed MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery","./jquery.validate.min"],a):"object"==typeof module&&module.exports?module.exports=a(require("jquery")):a(jQuery)}(function(a){return function(){function b(a){return a.replace(/<.[^<>]*?>/g," ").replace(/ | /gi," ").replace(/[.(),;:!?%#$'\"_+=\/\-“”’]*/g,"")}a.validator.addMethod("maxWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length<=d},a.validator.format("Please enter {0} words or less.")),a.validator.addMethod("minWords",function(a,c,d){return this.optional(c)||b(a).match(/\b\w+\b/g).length>=d},a.validator.format("Please enter at least {0} words.")),a.validator.addMethod("rangeWords",function(a,c,d){var e=b(a),f=/\b\w+\b/g;return this.optional(c)||e.match(f).length>=d[0]&&e.match(f).length<=d[1]},a.validator.format("Please enter between {0} and {1} words."))}(),a.validator.addMethod("accept",function(b,c,d){var e,f,g,h="string"==typeof d?d.replace(/\s/g,""):"image/*",i=this.optional(c);if(i)return i;if("file"===a(c).attr("type")&&(h=h.replace(/[\-\[\]\/\{\}\(\)\+\?\.\\\^\$\|]/g,"\\$&").replace(/,/g,"|").replace(/\/\*/g,"/.*"),c.files&&c.files.length))for(g=new RegExp(".?("+h+")$","i"),e=0;e9?"0":f,g="JABCDEFGHI".substr(f,1).toString(),i.match(/[ABEH]/)?k===f:i.match(/[KPQS]/)?k===g:k===f||k===g},"Please specify a valid CIF number."),a.validator.addMethod("cpfBR",function(a){if(a=a.replace(/([~!@#$%^&*()_+=`{}\[\]\-|\\:;'<>,.\/? ])+/g,""),11!==a.length)return!1;var b,c,d,e,f=0;if(b=parseInt(a.substring(9,10),10),c=parseInt(a.substring(10,11),10),d=function(a,b){var c=10*a%11;return 10!==c&&11!==c||(c=0),c===b},""===a||"00000000000"===a||"11111111111"===a||"22222222222"===a||"33333333333"===a||"44444444444"===a||"55555555555"===a||"66666666666"===a||"77777777777"===a||"88888888888"===a||"99999999999"===a)return!1;for(e=1;e<=9;e++)f+=parseInt(a.substring(e-1,e),10)*(11-e);if(d(f,b)){for(f=0,e=1;e<=10;e++)f+=parseInt(a.substring(e-1,e),10)*(12-e);return d(f,c)}return!1},"Please specify a valid CPF number"),a.validator.addMethod("creditcard",function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9 \-]+/.test(a))return!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},"Please enter a valid credit card number."),a.validator.addMethod("creditcardtypes",function(a,b,c){if(/[^0-9\-]+/.test(a))return!1;a=a.replace(/\D/g,"");var d=0;return c.mastercard&&(d|=1),c.visa&&(d|=2),c.amex&&(d|=4),c.dinersclub&&(d|=8),c.enroute&&(d|=16),c.discover&&(d|=32),c.jcb&&(d|=64),c.unknown&&(d|=128),c.all&&(d=255),1&d&&/^(5[12345])/.test(a)?16===a.length:2&d&&/^(4)/.test(a)?16===a.length:4&d&&/^(3[47])/.test(a)?15===a.length:8&d&&/^(3(0[012345]|[68]))/.test(a)?14===a.length:16&d&&/^(2(014|149))/.test(a)?15===a.length:32&d&&/^(6011)/.test(a)?16===a.length:64&d&&/^(3)/.test(a)?16===a.length:64&d&&/^(2131|1800)/.test(a)?15===a.length:!!(128&d)},"Please enter a valid credit card number."),a.validator.addMethod("currency",function(a,b,c){var d,e="string"==typeof c,f=e?c:c[0],g=!!e||c[1];return f=f.replace(/,/g,""),f=g?f+"]":f+"]?",d="^["+f+"([1-9]{1}[0-9]{0,2}(\\,[0-9]{3})*(\\.[0-9]{0,2})?|[1-9]{1}[0-9]{0,}(\\.[0-9]{0,2})?|0(\\.[0-9]{0,2})?|(\\.[0-9]{1,2})?)$",d=new RegExp(d),this.optional(b)||d.test(a)},"Please specify a valid currency"),a.validator.addMethod("dateFA",function(a,b){return this.optional(b)||/^[1-4]\d{3}\/((0?[1-6]\/((3[0-1])|([1-2][0-9])|(0?[1-9])))|((1[0-2]|(0?[7-9]))\/(30|([1-2][0-9])|(0?[1-9]))))$/.test(a)},a.validator.messages.date),a.validator.addMethod("dateITA",function(a,b){var c,d,e,f,g,h=!1,i=/^\d{1,2}\/\d{1,2}\/\d{4}$/;return i.test(a)?(c=a.split("/"),d=parseInt(c[0],10),e=parseInt(c[1],10),f=parseInt(c[2],10),g=new Date(Date.UTC(f,e-1,d,12,0,0,0)),h=g.getUTCFullYear()===f&&g.getUTCMonth()===e-1&&g.getUTCDate()===d):h=!1,this.optional(b)||h},a.validator.messages.date),a.validator.addMethod("dateNL",function(a,b){return this.optional(b)||/^(0?[1-9]|[12]\d|3[01])[\.\/\-](0?[1-9]|1[012])[\.\/\-]([12]\d)?(\d\d)$/.test(a)},a.validator.messages.date),a.validator.addMethod("extension",function(a,b,c){return c="string"==typeof c?c.replace(/,/g,"|"):"png|jpe?g|gif",this.optional(b)||a.match(new RegExp("\\.("+c+")$","i"))},a.validator.format("Please enter a value with a valid extension.")),a.validator.addMethod("giroaccountNL",function(a,b){return this.optional(b)||/^[0-9]{1,7}$/.test(a)},"Please specify a valid giro account number"),a.validator.addMethod("iban",function(a,b){if(this.optional(b))return!0;var c,d,e,f,g,h,i,j,k,l=a.replace(/ /g,"").toUpperCase(),m="",n=!0,o="",p="",q=5;if(l.length9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)7(?:[1345789]\d{2}|624)\s?\d{3}\s?\d{3})$/)},"Please specify a valid mobile number"),a.validator.addMethod("netmask",function(a,b){return this.optional(b)||/^(254|252|248|240|224|192|128)\.0\.0\.0|255\.(254|252|248|240|224|192|128|0)\.0\.0|255\.255\.(254|252|248|240|224|192|128|0)\.0|255\.255\.255\.(254|252|248|240|224|192|128|0)/i.test(a)},"Please enter a valid netmask."),a.validator.addMethod("nieES",function(a,b){"use strict";if(this.optional(b))return!0;var c,d=new RegExp(/^[MXYZ]{1}[0-9]{7,8}[TRWAGMYFPDXBNJZSQVHLCKET]{1}$/gi),e="TRWAGMYFPDXBNJZSQVHLCKET",f=a.substr(a.length-1).toUpperCase();return a=a.toString().toUpperCase(),!(a.length>10||a.length<9||!d.test(a))&&(a=a.replace(/^[X]/,"0").replace(/^[Y]/,"1").replace(/^[Z]/,"2"),c=9===a.length?a.substr(0,8):a.substr(0,9),e.charAt(parseInt(c,10)%23)===f)},"Please specify a valid NIE number."),a.validator.addMethod("nifES",function(a,b){"use strict";return!!this.optional(b)||(a=a.toUpperCase(),!!a.match("((^[A-Z]{1}[0-9]{7}[A-Z0-9]{1}$|^[T]{1}[A-Z0-9]{8}$)|^[0-9]{8}[A-Z]{1}$)")&&(/^[0-9]{8}[A-Z]{1}$/.test(a)?"TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,0)%23)===a.charAt(8):!!/^[KLM]{1}/.test(a)&&a[8]==="TRWAGMYFPDXBNJZSQVHLCKE".charAt(a.substring(8,1)%23)))},"Please specify a valid NIF number."),a.validator.addMethod("nipPL",function(a){"use strict";if(a=a.replace(/[^0-9]/g,""),10!==a.length)return!1;for(var b=[6,5,7,2,3,4,5,6,7],c=0,d=0;d<9;d++)c+=b[d]*a[d];var e=c%11,f=10===e?0:e;return f===parseInt(a[9],10)},"Please specify a valid NIP number."),a.validator.addMethod("notEqualTo",function(b,c,d){return this.optional(c)||!a.validator.methods.equalTo.call(this,b,c,d)},"Please enter a different value, values must not be the same."),a.validator.addMethod("nowhitespace",function(a,b){return this.optional(b)||/^\S+$/i.test(a)},"No white space please"),a.validator.addMethod("pattern",function(a,b,c){return!!this.optional(b)||("string"==typeof c&&(c=new RegExp("^(?:"+c+")$")),c.test(a))},"Invalid format."),a.validator.addMethod("phoneNL",function(a,b){return this.optional(b)||/^((\+|00(\s|\s?\-\s?)?)31(\s|\s?\-\s?)?(\(0\)[\-\s]?)?|0)[1-9]((\s|\s?\-\s?)?[0-9]){8}$/.test(a)},"Please specify a valid phone number."),a.validator.addMethod("phonesUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?|0)(?:1\d{8,9}|[23]\d{9}|7(?:[1345789]\d{8}|624\d{6})))$/)},"Please specify a valid uk phone number"),a.validator.addMethod("phoneUK",function(a,b){return a=a.replace(/\(|\)|\s+|-/g,""),this.optional(b)||a.length>9&&a.match(/^(?:(?:(?:00\s?|\+)44\s?)|(?:\(?0))(?:\d{2}\)?\s?\d{4}\s?\d{4}|\d{3}\)?\s?\d{3}\s?\d{3,4}|\d{4}\)?\s?(?:\d{5}|\d{3}\s?\d{3})|\d{5}\)?\s?\d{4,5})$/)},"Please specify a valid phone number"),a.validator.addMethod("phoneUS",function(a,b){return a=a.replace(/\s+/g,""),this.optional(b)||a.length>9&&a.match(/^(\+?1-?)?(\([2-9]([02-9]\d|1[02-9])\)|[2-9]([02-9]\d|1[02-9]))-?[2-9]([02-9]\d|1[02-9])-?\d{4}$/)},"Please specify a valid phone number"),a.validator.addMethod("postalcodeBR",function(a,b){return this.optional(b)||/^\d{2}.\d{3}-\d{3}?$|^\d{5}-?\d{3}?$/.test(a)},"Informe um CEP válido."),a.validator.addMethod("postalCodeCA",function(a,b){return this.optional(b)||/^[ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ] *\d[ABCEGHJKLMNPRSTVWXYZ]\d$/i.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeIT",function(a,b){return this.optional(b)||/^\d{5}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postalcodeNL",function(a,b){return this.optional(b)||/^[1-9][0-9]{3}\s?[a-zA-Z]{2}$/.test(a)},"Please specify a valid postal code"),a.validator.addMethod("postcodeUK",function(a,b){return this.optional(b)||/^((([A-PR-UWYZ][0-9])|([A-PR-UWYZ][0-9][0-9])|([A-PR-UWYZ][A-HK-Y][0-9])|([A-PR-UWYZ][A-HK-Y][0-9][0-9])|([A-PR-UWYZ][0-9][A-HJKSTUW])|([A-PR-UWYZ][A-HK-Y][0-9][ABEHMNPRVWXY]))\s?([0-9][ABD-HJLNP-UW-Z]{2})|(GIR)\s?(0AA))$/i.test(a)},"Please specify a valid UK postcode"),a.validator.addMethod("require_from_group",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_req_grp")?f.data("valid_req_grp"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length>=d[0];return f.data("valid_req_grp",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),h},a.validator.format("Please fill at least {0} of these fields.")),a.validator.addMethod("skip_or_fill_minimum",function(b,c,d){var e=a(d[1],c.form),f=e.eq(0),g=f.data("valid_skip")?f.data("valid_skip"):a.extend({},this),h=e.filter(function(){return g.elementValue(this)}).length,i=0===h||h>=d[0];return f.data("valid_skip",g),a(c).data("being_validated")||(e.data("being_validated",!0),e.each(function(){g.element(this)}),e.data("being_validated",!1)),i},a.validator.format("Please either skip these fields or fill at least {0} of them.")),a.validator.addMethod("stateUS",function(a,b,c){var d,e="undefined"==typeof c,f=!e&&"undefined"!=typeof c.caseSensitive&&c.caseSensitive,g=!e&&"undefined"!=typeof c.includeTerritories&&c.includeTerritories,h=!e&&"undefined"!=typeof c.includeMilitary&&c.includeMilitary;return d=g||h?g&&h?"^(A[AEKLPRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":g?"^(A[KLRSZ]|C[AOT]|D[CE]|FL|G[AU]|HI|I[ADLN]|K[SY]|LA|M[ADEINOPST]|N[CDEHJMVY]|O[HKR]|P[AR]|RI|S[CD]|T[NX]|UT|V[AIT]|W[AIVY])$":"^(A[AEKLPRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$":"^(A[KLRZ]|C[AOT]|D[CE]|FL|GA|HI|I[ADLN]|K[SY]|LA|M[ADEINOST]|N[CDEHJMVY]|O[HKR]|PA|RI|S[CD]|T[NX]|UT|V[AT]|W[AIVY])$",d=f?new RegExp(d):new RegExp(d,"i"),this.optional(b)||d.test(a)},"Please specify a valid state"),a.validator.addMethod("strippedminlength",function(b,c,d){return a(b).text().length>=d},a.validator.format("Please enter at least {0} characters")),a.validator.addMethod("time",function(a,b){return this.optional(b)||/^([01]\d|2[0-3]|[0-9])(:[0-5]\d){1,2}$/.test(a)},"Please enter a valid time, between 00:00 and 23:59"),a.validator.addMethod("time12h",function(a,b){return this.optional(b)||/^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test(a)},"Please enter a valid time in 12-hour am/pm format"),a.validator.addMethod("url2",function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)*(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)},a.validator.messages.url),a.validator.addMethod("vinUS",function(a){if(17!==a.length)return!1;var b,c,d,e,f,g,h=["A","B","C","D","E","F","G","H","J","K","L","M","N","P","R","S","T","U","V","W","X","Y","Z"],i=[1,2,3,4,5,6,7,8,1,2,3,4,5,7,9,2,3,4,5,6,7,8,9],j=[8,7,6,5,4,3,2,10,0,9,8,7,6,5,4,3,2],k=0;for(b=0;b<17;b++){if(e=j[b],d=a.slice(b,b+1),8===b&&(g=d),isNaN(d)){for(c=0;c