< 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>
 12225public class JintJavaScriptEvaluator(IConfiguration configuration, INotificationSender mediator, IOptions<JintOptions> s
 26    : IJavaScriptEvaluator
 27{
 12228    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    {
 20839        var engine = await GetConfiguredEngine(configureEngine, context, options, cancellationToken);
 20840        await mediator.SendAsync(new EvaluatingJavaScript(engine, context, expression), cancellationToken);
 20841        var result = ExecuteExpressionAndGetResult(engine, expression);
 20542        await mediator.SendAsync(new EvaluatedJavaScript(engine, context, expression, result), cancellationToken);
 43
 20544        return result.ConvertTo(returnType);
 20545    }
 46
 47    private async Task<Engine> GetConfiguredEngine(Action<Engine>? configureEngine, ExpressionExecutionContext context, 
 48    {
 20849        options ??= new();
 50
 20851        var engineOptions = new Jint.Options
 20852        {
 20853            ExperimentalFeatures = ExperimentalFeature.TaskInterop
 20854        };
 55
 20856        ConfigureClrAccess(engineOptions);
 20857        ConfigureObjectWrapper(engineOptions);
 20858        ConfigureObjectConverters(engineOptions);
 59
 20860        await mediator.SendAsync(new CreatingJavaScriptEngine(engineOptions, context), cancellationToken);
 20861        _jintOptions.ConfigureEngineOptionsCallback(engineOptions, context);
 62
 20863        var engine = new Engine(engineOptions);
 64
 20865        configureEngine?.Invoke(engine);
 20866        ConfigureArgumentGetters(engine, options);
 20867        ConfigureConfigurationAccess(engine);
 20868        _jintOptions.ConfigureEngineCallback(engine, context);
 69
 20870        return engine;
 20871    }
 72
 73    private void ConfigureClrAccess(Jint.Options options)
 74    {
 20875        if (_jintOptions.AllowClrAccess)
 2376            options.AllowClr();
 20877    }
 78
 79    private void ConfigureObjectWrapper(Jint.Options options)
 80    {
 20881        options.SetWrapObjectHandler((engine, target, type) =>
 20882        {
 38083            var instance = ObjectWrapper.Create(engine, target);
 20884
 38085            if (ObjectArrayHelper.DetermineIfObjectIsArrayLikeClrCollection(target.GetType()))
 36186                instance.Prototype = engine.Intrinsics.Array.PrototypeObject;
 20887
 38088            return instance;
 20889        });
 20890    }
 91
 92    private void ConfigureObjectConverters(Jint.Options options)
 93    {
 20894        options.Interop.ObjectConverters.AddRange([new ByteArrayConverter(), new EnumToStringConverter(), new JsonElemen
 20895    }
 96
 97    private void ConfigureArgumentGetters(Engine engine, ExpressionEvaluatorOptions options)
 98    {
 42299        foreach (var argument in options.Arguments)
 6100            engine.SetValue($"get{argument.Key}", (Func<object?>)(() => argument.Value));
 208101    }
 102
 103    private void ConfigureConfigurationAccess(Engine engine)
 104    {
 208105        if (_jintOptions.AllowConfigurationAccess)
 0106            engine.SetValue("getConfig", (Func<string, object?>)(name => configuration.GetSection(name).Value));
 208107    }
 108
 109    private object? ExecuteExpressionAndGetResult(Engine engine, string expression)
 110    {
 208111        var preparedScript = GetOrCreatePrepareScript(expression);
 206112        var result = engine.Evaluate(preparedScript);
 205113        return result.UnwrapIfPromise().ToObject();
 114    }
 115
 116    private Prepared<Script> GetOrCreatePrepareScript(string expression)
 117    {
 208118        var cacheKey = "jint:script:" + Hash(expression);
 119
 208120        return memoryCache.GetOrCreate(cacheKey, entry =>
 208121        {
 148122            if (_jintOptions.ScriptCacheTimeout.HasValue)
 148123                entry.SetSlidingExpiration(_jintOptions.ScriptCacheTimeout.Value);
 208124
 148125            return PrepareScript(expression);
 208126        })!;
 127    }
 128
 129    private Prepared<Script> PrepareScript(string expression)
 130    {
 148131        var prepareOptions = new ScriptPreparationOptions
 148132        {
 148133            ParsingOptions = new()
 148134            {
 148135                AllowReturnOutsideFunction = true
 148136            }
 148137        };
 148138        return Engine.PrepareScript(expression, options: prepareOptions);
 139    }
 140
 141    private string Hash(string input)
 142    {
 208143        var bytes = Encoding.UTF8.GetBytes(input);
 208144        var hash = SHA256.HashData(bytes);
 208145        return Convert.ToBase64String(hash);
 146    }
 147}