2
0
Эх сурвалжийг харах

Add extension point for custom Function.toString() logic (#2043)

Marko Lahma 6 сар өмнө
parent
commit
8ebce9a0f7

+ 26 - 0
Jint.Tests.PublicInterface/FunctionToStringTest.cs

@@ -0,0 +1,26 @@
+using FluentAssertions;
+
+namespace Jint.Tests.PublicInterface;
+
+public class FunctionToStringTest
+{
+    [Fact]
+    public void CanRegisterCustomFunctionToString()
+    {
+        const string Code = "var x = 1; var y = 3; function testFunction() { return 'Something'; }; testFunction.toString(); var z = x + y;";
+
+        // we can rewrite back with AST to get custom formatting
+        var engine = new Engine(options =>
+            options.Host.FunctionToStringHandler = (function, node) => node.ToJavaScript(KnRJavaScriptTextFormatterOptions.Default)
+        );
+
+        engine.Evaluate(Code).AsString().Should().Be($"function testFunction() {{{Environment.NewLine}  return 'Something';{Environment.NewLine}}}");
+
+        // or we can brute force the original input when we use node's location information
+        engine = new Engine(options =>
+            options.Host.FunctionToStringHandler = (function, node) => Code.Substring(node.Start, node.End - node.Start)
+        );
+
+        engine.Evaluate(Code).AsString().Should().Be("function testFunction() { return 'Something'; }");
+    }
+}

+ 6 - 2
Jint/Native/Function/Function.cs

@@ -368,7 +368,11 @@ public abstract partial class Function : ObjectInstance, ICallable
 
 
     public override string ToString()
     public override string ToString()
     {
     {
-        // TODO no way to extract SourceText from Esprima at the moment, just returning native code
+        if (_functionDefinition?.Function is Node node && _engine.Options.Host.FunctionToStringHandler(this, node) is { } s)
+        {
+            return s;
+        }
+
         var nameValue = _nameDescriptor != null ? UnwrapJsValue(_nameDescriptor) : JsString.Empty;
         var nameValue = _nameDescriptor != null ? UnwrapJsValue(_nameDescriptor) : JsString.Empty;
         var name = "";
         var name = "";
         if (!nameValue.IsUndefined())
         if (!nameValue.IsUndefined())
@@ -378,7 +382,7 @@ public abstract partial class Function : ObjectInstance, ICallable
 
 
         name = name.TrimStart(_functionNameTrimStartChars);
         name = name.TrimStart(_functionNameTrimStartChars);
 
 
-        return "function " + name + "() { [native code] }";
+        return $"function {name}() {{ [native code] }}";
     }
     }
 
 
     private sealed class ObjectInstanceWithConstructor : ObjectInstance
     private sealed class ObjectInstanceWithConstructor : ObjectInstance

+ 2 - 2
Jint/Options.Extensions.cs

@@ -106,7 +106,7 @@ public static class OptionsExtensions
     /// </summary>
     /// </summary>
     public static Options DisableStringCompilation(this Options options, bool disable = true)
     public static Options DisableStringCompilation(this Options options, bool disable = true)
     {
     {
-        options.StringCompilationAllowed = !disable;
+        options.Host.StringCompilationAllowed = !disable;
         return options;
         return options;
     }
     }
 
 
@@ -295,4 +295,4 @@ public static class OptionsExtensions
         options.Modules.ModuleLoader = moduleLoader;
         options.Modules.ModuleLoader = moduleLoader;
         return options;
         return options;
     }
     }
-}
+}

+ 23 - 3
Jint/Options.cs

@@ -3,6 +3,7 @@ using System.Globalization;
 using System.Linq;
 using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native;
+using Jint.Native.Function;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
@@ -47,7 +48,7 @@ public class Options
     /// <summary>
     /// <summary>
     /// Host options.
     /// Host options.
     /// </summary>
     /// </summary>
-    internal HostOptions Host { get; } = new();
+    public HostOptions Host { get; } = new();
 
 
     /// <summary>
     /// <summary>
     /// Module options
     /// Module options
@@ -64,7 +65,6 @@ public class Options
     /// </summary>
     /// </summary>
     public CultureInfo Culture { get; set; } = _defaultCulture;
     public CultureInfo Culture { get; set; } = _defaultCulture;
 
 
-
     /// <summary>
     /// <summary>
     /// Configures a time system to use. Defaults to DefaultTimeSystem using local time.
     /// Configures a time system to use. Defaults to DefaultTimeSystem using local time.
     /// </summary>
     /// </summary>
@@ -94,7 +94,12 @@ public class Options
     /// <remarks>
     /// <remarks>
     /// https://tc39.es/ecma262/#sec-hostensurecancompilestrings
     /// https://tc39.es/ecma262/#sec-hostensurecancompilestrings
     /// </remarks>
     /// </remarks>
-    public bool StringCompilationAllowed { get; set; } = true;
+    [Obsolete("Use Options.Host.StringCompilationAllowed")]
+    public bool StringCompilationAllowed
+    {
+        get => Host.StringCompilationAllowed;
+        set => Host.StringCompilationAllowed = value;
+    }
 
 
     /// <summary>
     /// <summary>
     /// Options for the built-in JSON (de)serializer which
     /// Options for the built-in JSON (de)serializer which
@@ -436,6 +441,21 @@ public class Options
     public class HostOptions
     public class HostOptions
     {
     {
         internal Func<Engine, Host> Factory { get; set; } = _ => new Host();
         internal Func<Engine, Host> Factory { get; set; } = _ => new Host();
+
+        /// <summary>
+        /// Whether calling 'eval' with custom code and function constructors taking function code as string is allowed.
+        /// Defaults to true.
+        /// </summary>
+        /// <remarks>
+        /// https://tc39.es/ecma262/#sec-hostensurecancompilestrings
+        /// </remarks>
+        public bool StringCompilationAllowed { get; set; } = true;
+
+        /// <summary>
+        /// Possibility to override Jint's default function() { [native code] } format for functions using AST Node.
+        /// If callback return null, Jint will use its own default logic.
+        /// </summary>
+        public Func<Function, Node, string?> FunctionToStringHandler { get; set; } = (_, _) => null;
     }
     }
 
 
     /// <summary>
     /// <summary>

+ 2 - 2
Jint/Runtime/Host.cs

@@ -110,7 +110,7 @@ public class Host
     /// </summary>
     /// </summary>
     public virtual void EnsureCanCompileStrings(Realm callerRealm, Realm evalRealm)
     public virtual void EnsureCanCompileStrings(Realm callerRealm, Realm evalRealm)
     {
     {
-        if (!Engine.Options.StringCompilationAllowed)
+        if (!Engine.Options.Host.StringCompilationAllowed)
         {
         {
             ExceptionHelper.ThrowJavaScriptException(callerRealm.Intrinsics.TypeError, "String compilation has been disabled in engine options");
             ExceptionHelper.ThrowJavaScriptException(callerRealm.Intrinsics.TypeError, "String compilation has been disabled in engine options");
         }
         }
@@ -219,4 +219,4 @@ public class Host
     }
     }
 }
 }
 
 
-internal sealed record JobCallback(ICallable Callback, object? HostDefined);
+internal sealed record JobCallback(ICallable Callback, object? HostDefined);