| | | 1 | | using Jint; |
| | | 2 | | using Jint.Runtime.Interop; |
| | | 3 | | |
| | | 4 | | // ReSharper disable once CheckNamespace |
| | | 5 | | namespace Elsa.Extensions; |
| | | 6 | | |
| | | 7 | | /// <summary> |
| | | 8 | | /// Extends <see cref="Jint.Options"/>. |
| | | 9 | | /// </summary> |
| | | 10 | | public static class EngineOptionsExtensions |
| | | 11 | | { |
| | | 12 | | /// <summary> |
| | | 13 | | /// Registers the specified type <c>T</c> with every engine built from these options. |
| | | 14 | | /// </summary> |
| | 1710 | 15 | | public static Jint.Options RegisterType<T>(this Jint.Options options) => options.RegisterType(typeof(T)); |
| | | 16 | | |
| | | 17 | | /// <summary> |
| | | 18 | | /// Registers the specified type with every engine built from these options, under its type name. |
| | | 19 | | /// </summary> |
| | | 20 | | /// <remarks> |
| | | 21 | | /// <para> |
| | | 22 | | /// The type reference is built the first time a script reads the name. Registering a type therefore costs a |
| | | 23 | | /// property definition rather than the reflection needed to describe the type, which matters because a fresh |
| | | 24 | | /// engine is built for every expression evaluation and most expressions use none of the registered types. |
| | | 25 | | /// </para> |
| | | 26 | | /// <para> |
| | | 27 | | /// Types whose name cannot be written as a JavaScript identifier are skipped: a constructed generic type such |
| | | 28 | | /// as <c>IDictionary<string, object></c> is named <c>IDictionary`2</c> and an array type such as |
| | | 29 | | /// <c>byte[]</c> is named <c>Byte[]</c>. Registered, those would only be reachable through bracket notation — |
| | | 30 | | /// <c>globalThis['Byte[]']</c> — and every constructed generic type of the same arity would claim the same |
| | | 31 | | /// global. |
| | | 32 | | /// </para> |
| | | 33 | | /// <para> |
| | | 34 | | /// Registering the same name twice is allowed and the last registration wins. Several handlers contribute type |
| | | 35 | | /// registrations and their sets overlap — <c>Guid</c>, <c>DateTime</c>, <c>DateTimeOffset</c>, <c>TimeSpan</c> |
| | | 36 | | /// and <c>LogPersistenceMode</c> are registered both as common types and as workflow variable types — but a |
| | | 37 | | /// duplicate now costs one property definition and describes nothing, and both registrations name the same |
| | | 38 | | /// <see cref="Type"/>, so which one survives is not observable. Registration happens while the engine is being |
| | | 39 | | /// built, so every host extension point — the per-evaluation <c>configureEngine</c> callback, |
| | | 40 | | /// <c>JintOptions.ConfigureEngine</c> and <c>JintOptions.ConfigureEngineOptions</c> — runs afterwards and a |
| | | 41 | | /// global installed there is never replaced by a type registration. |
| | | 42 | | /// </para> |
| | | 43 | | /// </remarks> |
| | | 44 | | public static Jint.Options RegisterType(this Jint.Options options, Type type) |
| | | 45 | | { |
| | 5868 | 46 | | var name = type.Name; |
| | | 47 | | |
| | 5868 | 48 | | if (!IsUsableAsIdentifier(name)) |
| | 855 | 49 | | return options; |
| | | 50 | | |
| | 5041 | 51 | | return options.AddLazyGlobal(name, engine => TypeReference.CreateTypeReference(engine, type)); |
| | | 52 | | } |
| | | 53 | | |
| | | 54 | | private static bool IsUsableAsIdentifier(string name) |
| | | 55 | | { |
| | 5868 | 56 | | if (string.IsNullOrEmpty(name) || (!char.IsLetter(name[0]) && name[0] != '_' && name[0] != '$')) |
| | 0 | 57 | | return false; |
| | | 58 | | |
| | 125451 | 59 | | foreach (var c in name) |
| | | 60 | | { |
| | 57285 | 61 | | if (!char.IsLetterOrDigit(c) && c != '_' && c != '$') |
| | 855 | 62 | | return false; |
| | | 63 | | } |
| | | 64 | | |
| | 5013 | 65 | | return true; |
| | | 66 | | } |
| | | 67 | | } |