< Summary

Information
Class: Elsa.Expressions.JavaScript.Services.JintJavaScriptEvaluator
Assembly: Elsa.Expressions.JavaScript
File(s): /home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs
Line coverage
98%
Covered lines: 67
Uncovered lines: 1
Coverable lines: 68
Total lines: 147
Line coverage: 98.5%
Branch coverage
92%
Covered branches: 13
Total branches: 14
Branch coverage: 92.8%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
EvaluateAsync()100%11100%
GetConfiguredEngine()100%44100%
ConfigureClrAccess(...)100%22100%
ConfigureObjectWrapper(...)100%22100%
ConfigureObjectConverters(...)100%11100%
ConfigureArgumentGetters(...)100%22100%
ConfigureConfigurationAccess(...)50%2266.66%
ExecuteExpressionAndGetResult(...)100%11100%
GetOrCreatePrepareScript(...)100%22100%
PrepareScript(...)100%11100%
Hash(...)100%11100%

File(s)

/home/runner/work/elsa-core/elsa-core/src/modules/Elsa.Expressions.JavaScript/Services/JintJavaScriptEvaluator.cs

#LineLine coverage
 1using System.Diagnostics.CodeAnalysis;
 2using System.Security.Cryptography;
 3using System.Text;
 4using Acornima.Ast;
 5using Elsa.Expressions.Helpers;
 6using Elsa.Expressions.Models;
 7using Elsa.Expressions.JavaScript.Contracts;
 8using Elsa.Expressions.JavaScript.Helpers;
 9using Elsa.Expressions.JavaScript.Notifications;
 10using Elsa.Expressions.JavaScript.ObjectConverters;
 11using Elsa.Expressions.JavaScript.Options;
 12using Elsa.Mediator.Contracts;
 13using Jint;
 14using Jint.Runtime.Interop;
 15using Microsoft.Extensions.Caching.Memory;
 16using Microsoft.Extensions.Configuration;
 17using Microsoft.Extensions.Options;
 18
 19// ReSharper disable ConvertClosureToMethodGroup
 20namespace Elsa.Expressions.JavaScript.Services;
 21
 22/// <summary>
 23/// Provides a JavaScript evaluator using Jint.
 24/// </summary>
 11925public class JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions<JintOptions> s
 26    : IJavaScriptEvaluator
 27{
 11928    private readonly JintOptions _jintOptions = scriptOptions.Value;
 29
 30    /// <inheritdoc />
 31    [RequiresUnreferencedCode("The Jint library uses reflection and can't be statically analyzed.")]
 32    public async Task<object?> EvaluateAsync(string expression,
 33        Type returnType,
 34        ExpressionExecutionContext context,
 35        ExpressionEvaluatorOptions? options = null,
 36        Action<Engine>? configureEngine = null,
 37        CancellationToken cancellationToken = default)
 38    {
 20539        var engine = await GetConfiguredEngine(configureEngine, context, options, cancellationToken);
 20540        await mediator.SendAsync(new EvaluatingJavaScript(engine, context, expression), cancellationToken);
 20541        var result = ExecuteExpressionAndGetResult(engine, expression);
 20242        await mediator.SendAsync(new EvaluatedJavaScript(engine, context, expression, result), cancellationToken);
 43
 20244        return result.ConvertTo(returnType);
 20245    }
 46
 47    private async Task<Engine> GetConfiguredEngine(Action<Engine>? configureEngine, ExpressionExecutionContext context, 
 48    {
 20549        options ??= new();
 50
 20551        var engineOptions = new Jint.Options
 20552        {
 20553            ExperimentalFeatures = ExperimentalFeature.TaskInterop
 20554        };
 55
 20556        ConfigureClrAccess(engineOptions);
 20557        ConfigureObjectWrapper(engineOptions);
 20558        ConfigureObjectConverters(engineOptions);
 59
 20560        await mediator.SendAsync(new CreatingJavaScriptEngine(engineOptions, context), cancellationToken);
 20561        _jintOptions.ConfigureEngineOptionsCallback(engineOptions, context);
 62
 20563        var engine = new Engine(engineOptions);
 64
 20565        configureEngine?.Invoke(engine);
 20566        ConfigureArgumentGetters(engine, options);
 20567        ConfigureConfigurationAccess(engine);
 20568        _jintOptions.ConfigureEngineCallback(engine, context);
 69
 20570        return engine;
 20571    }
 72
 73    private void ConfigureClrAccess(Jint.Options options)
 74    {
 20575        if (_jintOptions.AllowClrAccess)
 1976            options.AllowClr();
 20577    }
 78
 79    private void ConfigureObjectWrapper(Jint.Options options)
 80    {
 20581        options.SetWrapObjectHandler((engine, target, type) =>
 20582        {
 37383            var instance = ObjectWrapper.Create(engine, target);
 20584
 37385            if (ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection(target.GetType()))
 35486                instance.Prototype = engine.Intrinsics.Array.PrototypeObject;
 20587
 37388            return instance;
 20589        });
 20590    }
 91
 92    private void ConfigureObjectConverters(Jint.Options options)
 93    {
 20594        options.Interop.ObjectConverters.AddRange([new ByteArrayConverter(), new EnumToStringConverter(), new JsonElemen
 20595    }
 96
 97    private void ConfigureArgumentGetters(Engine engine, ExpressionEvaluatorOptions options)
 98    {
 41699        foreach (var argument in options.Arguments)
 6100            engine.SetValue($"get{argument.Key}", (Func<object?>)(() => argument.Value));
 205101    }
 102
 103    private void ConfigureConfigurationAccess(Engine engine)
 104    {
 205105        if (_jintOptions.AllowConfigurationAccess)
 0106            engine.SetValue("getConfig", (Func<string, object?>)(name => configuration.GetSection(name).Value));
 205107    }
 108
 109    private object? ExecuteExpressionAndGetResult(Engine engine, string expression)
 110    {
 205111        var preparedScript = GetOrCreatePrepareScript(expression);
 203112        var result = engine.Evaluate(preparedScript);
 202113        return result.UnwrapIfPromise().ToObject();
 114    }
 115
 116    private Prepared<Script> GetOrCreatePrepareScript(string expression)
 117    {
 205118        var cacheKey = "jint:script:" + Hash(expression);
 119
 205120        return memoryCache.GetOrCreate(cacheKey, entry =>
 205121        {
 145122            if (_jintOptions.ScriptCacheTimeout.HasValue)
 145123                entry.SetSlidingExpiration(_jintOptions.ScriptCacheTimeout.Value);
 205124
 145125            return PrepareScript(expression);
 205126        })!;
 127    }
 128
 129    private Prepared<Script> PrepareScript(string expression)
 130    {
 145131        var prepareOptions = new ScriptPreparationOptions
 145132        {
 145133            ParsingOptions = new()
 145134            {
 145135                AllowReturnOutsideFunction = true
 145136            }
 145137        };
 145138        return Engine.PrepareScript(expression, options: prepareOptions);
 139    }
 140
 141    private string Hash(string input)
 142    {
 205143        var bytes = Encoding.UTF8.GetBytes(input);
 205144        var hash = SHA256.HashData(bytes);
 205145        return Convert.ToBase64String(hash);
 146    }
 147}