Browse Source

Improved support for defining custom JsString deriving type (#1404)

Marko Lahma 2 years ago
parent
commit
6839807204

+ 88 - 0
Jint.Tests.PublicInterface/RavenApiUsageTests.cs

@@ -161,4 +161,92 @@ public class RavenApiUsageTests
         Assert.Equal(106L, array.Length);
         Assert.True(array.All(x => x is JsNumber or JsUndefined or JsNumber or JsString or JsBoolean));
     }
+
+    // Checks different ways how string can be checked for equality without the need to materialize lazy value
+    [Fact]
+    public void CanInheritCustomString()
+    {
+        var engine = new Engine();
+
+        var str = new CustomString("the-value");
+        engine.SetValue("str", str);
+
+        var empty = new CustomString("");
+        engine.SetValue("empty", empty);
+
+        var obj = new JsObject(engine);
+        obj.Set("name", new CustomString("the name"));
+        engine.SetValue("obj", obj);
+
+        var array = new JsArray(engine, Enumerable.Range(1, 100).Select(x => new CustomString(x.ToString())).ToArray<JsValue>());
+        engine.SetValue("array", array);
+
+        Assert.True(engine.Evaluate("str ? true : false").AsBoolean());
+        Assert.False(engine.Evaluate("empty ? true : false").AsBoolean());
+
+        Assert.True(engine.Evaluate("array.includes('2')").AsBoolean());
+        Assert.True(engine.Evaluate("array.filter(x => x === '2').length > 0").AsBoolean());
+
+        engine.SetValue("objArray", new JsArray(engine, new JsValue[] { obj, obj }));
+        Assert.True(engine.Evaluate("objArray.filter(x => x.name === 'the name').length === 2").AsBoolean());
+
+        Assert.Equal(9, engine.Evaluate("str.length"));
+
+        Assert.True(engine.Evaluate("str == 'the-value'").AsBoolean());
+        Assert.True(engine.Evaluate("str === 'the-value'").AsBoolean());
+
+        Assert.True(engine.Evaluate("str.indexOf('value-too-long') === -1").AsBoolean());
+        Assert.True(engine.Evaluate("str.lastIndexOf('value-too-long') === -1").AsBoolean());
+        Assert.False(engine.Evaluate("str.startsWith('value-too-long')").AsBoolean());
+        Assert.False(engine.Evaluate("str.endsWith('value-too-long')").AsBoolean());
+        Assert.False(engine.Evaluate("str.includes('value-too-long')").AsBoolean());
+
+        Assert.True(engine.Evaluate("empty.trim() === ''").AsBoolean());
+        Assert.True(engine.Evaluate("empty.trimStart() === ''").AsBoolean());
+        Assert.True(engine.Evaluate("empty.trimEnd() === ''").AsBoolean());
+    }
+
+    private sealed class CustomString : JsString
+    {
+        private readonly string _value;
+
+        public CustomString(string value) : base(null)
+        {
+            _value = value;
+        }
+
+        public override string ToString()
+        {
+            // when called we know that we couldn't use fast paths
+            throw new InvalidOperationException("I don't want to be materialized!");
+        }
+
+        public override char this[int index] => _value[index];
+
+        public override int Length => _value.Length;
+
+        public override bool Equals(JsString obj)
+        {
+            return obj switch
+            {
+                CustomString customString => _value == customString._value,
+                _ => _value == obj.ToString()
+            };
+        }
+
+        public override bool IsLooselyEqual(JsValue value)
+        {
+            return value switch
+            {
+                CustomString customString => _value == customString._value,
+                JsString jsString => _value == jsString.ToString(),
+                _ => base.IsLooselyEqual(value)
+            };
+        }
+
+        public override int GetHashCode()
+        {
+            return _value.GetHashCode();
+        }
+    }
 }

+ 1 - 1
Jint/Engine.cs

@@ -182,7 +182,7 @@ namespace Jint
 
         public Engine SetValue(string name, string value)
         {
-            return SetValue(name, new JsString(value));
+            return SetValue(name, JsString.Create(value));
         }
 
         public Engine SetValue(string name, double value)

+ 4 - 5
Jint/Native/JsBigInt.cs

@@ -46,14 +46,13 @@ public sealed class JsBigInt : JsValue, IEquatable<JsBigInt>
     }
 
 
-    public override object ToObject()
-    {
-        return _value;
-    }
+    public override object ToObject() => _value;
+
+    internal override bool ToBoolean() => _value != 0;
 
     public static bool operator ==(JsBigInt a, double b)
     {
-        return a is not null && TypeConverter.IsIntegralNumber(b) && a._value == (long) b;
+        return TypeConverter.IsIntegralNumber(b) && a._value == (long) b;
     }
 
     public static bool operator !=(JsBigInt a, double b)

+ 3 - 4
Jint/Native/JsBoolean.cs

@@ -19,10 +19,9 @@ public sealed class JsBoolean : JsValue, IEquatable<JsBoolean>
 
     internal static JsBoolean Create(bool value) => value ? True : False;
 
-    public override object ToObject()
-    {
-        return _value ? BoxedTrue : BoxedFalse;
-    }
+    public override object ToObject() => _value ? BoxedTrue : BoxedFalse;
+
+    internal override bool ToBoolean() => _value;
 
     public override string ToString()
     {

+ 4 - 8
Jint/Native/JsNull.cs

@@ -8,15 +8,11 @@ public sealed class JsNull : JsValue, IEquatable<JsNull>
     {
     }
 
-    public override object ToObject()
-    {
-        return null!;
-    }
+    public override object ToObject() => null!;
 
-    public override string ToString()
-    {
-        return "null";
-    }
+    internal override bool ToBoolean() => false;
+
+    public override string ToString() => "null";
 
     public override bool IsLooselyEqual(JsValue value)
     {

+ 9 - 7
Jint/Native/JsNumber.cs

@@ -70,9 +70,16 @@ public sealed class JsNumber : JsValue, IEquatable<JsNumber>
         _value = value;
     }
 
-    public override object ToObject()
+    public override object ToObject() => _value;
+
+    internal override bool ToBoolean()
     {
-        return _value;
+        if (_type == InternalTypes.Integer)
+        {
+            return (int) _value != 0;
+        }
+
+        return _value != 0 && !double.IsNaN(_value);
     }
 
     internal static JsNumber Create(object value)
@@ -260,11 +267,6 @@ public sealed class JsNumber : JsValue, IEquatable<JsNumber>
         return Equals(obj as JsNumber);
     }
 
-    public override bool Equals(object? obj)
-    {
-        return Equals(obj as JsNumber);
-    }
-
     public bool Equals(JsNumber? other)
     {
         if (other is null)

+ 67 - 46
Jint/Native/JsString.cs

@@ -62,16 +62,26 @@ public class JsString : JsValue, IEquatable<JsString>
         _value = value.ToString();
     }
 
-    public override object ToObject()
+    public static bool operator ==(JsString? a, JsString? b)
     {
-        return _value;
+        if (a is not null)
+        {
+            return a.Equals(b);
+        }
+
+        if (a is null)
+        {
+            return b is null;
+        }
+
+        return b is not null && a.Equals(b);
     }
 
     public static bool operator ==(JsValue? a, JsString? b)
     {
         if (a is JsString s && b is not null)
         {
-            return s.ToString() == b.ToString();
+            return s.Equals(b);
         }
 
         if (a is null)
@@ -84,9 +94,9 @@ public class JsString : JsValue, IEquatable<JsString>
 
     public static bool operator ==(JsString? a, JsValue? b)
     {
-        if (a is not null && b is JsString s)
+        if (a is not null)
         {
-            return s.ToString() == b.ToString();
+            return a.Equals(b);
         }
 
         if (a is null)
@@ -107,25 +117,11 @@ public class JsString : JsValue, IEquatable<JsString>
         return !(a == b);
     }
 
-    public virtual char this[int index] => _value[index];
-
-    public virtual JsString Append(JsValue jsValue)
+    public static bool operator !=(JsString a, JsString b)
     {
-        return new ConcatenatedString(string.Concat(_value, TypeConverter.ToString(jsValue)));
-    }
-
-    internal virtual JsString EnsureCapacity(int capacity)
-    {
-        return new ConcatenatedString(_value, capacity);
-    }
-
-    internal virtual bool IsNullOrEmpty()
-    {
-        return string.IsNullOrEmpty(_value);
+        return !(a == b);
     }
 
-    public virtual int Length => _value.Length;
-
     internal static JsString Create(string value)
     {
         if (value.Length > 1)
@@ -191,21 +187,59 @@ public class JsString : JsValue, IEquatable<JsString>
         return new JsString(TypeConverter.ToString(value));
     }
 
-    public override string ToString()
+
+    public virtual char this[int index] => _value[index];
+
+    public virtual int Length => _value.Length;
+
+    internal virtual JsString Append(JsValue jsValue)
+    {
+        return new ConcatenatedString(string.Concat(ToString(), TypeConverter.ToString(jsValue)));
+    }
+
+    internal virtual JsString EnsureCapacity(int capacity)
+    {
+        return new ConcatenatedString(_value, capacity);
+    }
+
+    public sealed override object ToObject() => ToString();
+
+    internal sealed override bool ToBoolean()
     {
-        return _value;
+        return Length > 0;
     }
 
-    internal int IndexOf(string value, StringComparison comparisonType)
+    public override string ToString() => _value;
+
+    internal int IndexOf(string value, int startIndex = 0)
     {
-        return ToString().IndexOf(value, comparisonType);
+        if (Length - startIndex < value.Length)
+        {
+            return -1;
+        }
+        return ToString().IndexOf(value, startIndex, StringComparison.Ordinal);
     }
 
     internal int IndexOf(char value)
     {
+        if (Length == 0)
+        {
+            return -1;
+        }
         return ToString().IndexOf(value);
     }
 
+    internal bool StartsWith(string value, int start = 0)
+    {
+        return value.Length + start <= Length && ToString().AsSpan(start).StartsWith(value.AsSpan());
+    }
+
+    internal bool EndsWith(string value, int end = 0)
+    {
+        var start = end - value.Length;
+        return start >= 0 && ToString().AsSpan(start, value.Length).EndsWith(value.AsSpan());
+    }
+
     internal string Substring(int startIndex, int length)
     {
         return ToString().Substring(startIndex, length);
@@ -216,12 +250,12 @@ public class JsString : JsValue, IEquatable<JsString>
         return ToString().Substring(startIndex);
     }
 
-    public override bool Equals(JsValue? obj)
+    public sealed override bool Equals(JsValue? obj)
     {
         return Equals(obj as JsString);
     }
 
-    public bool Equals(JsString? other)
+    public virtual bool Equals(JsString? other)
     {
         if (other is null)
         {
@@ -251,7 +285,7 @@ public class JsString : JsValue, IEquatable<JsString>
         return base.IsLooselyEqual(value);
     }
 
-    public override bool Equals(object obj)
+    public sealed override bool Equals(object obj)
     {
         return Equals(obj as JsString);
     }
@@ -292,7 +326,7 @@ public class JsString : JsValue, IEquatable<JsString>
 
         public override char this[int index] => _stringBuilder?[index] ?? _value[index];
 
-        public override JsString Append(JsValue jsValue)
+        internal override JsString Append(JsValue jsValue)
         {
             var value = TypeConverter.ToString(jsValue);
             if (_stringBuilder == null)
@@ -312,17 +346,9 @@ public class JsString : JsValue, IEquatable<JsString>
             return this;
         }
 
-        internal override bool IsNullOrEmpty()
-        {
-            return _stringBuilder == null && string.IsNullOrEmpty(_value)
-                   || _stringBuilder != null && _stringBuilder.Length == 0;
-        }
-
         public override int Length => _stringBuilder?.Length ?? _value?.Length ?? 0;
 
-        public override object ToObject() => ToString();
-
-        public override bool Equals(JsValue? other)
+        public override bool Equals(JsString? other)
         {
             if (other is ConcatenatedString cs)
             {
@@ -346,17 +372,12 @@ public class JsString : JsValue, IEquatable<JsString>
                 return ToString() == cs.ToString();
             }
 
-            if (other is JsString jsString)
+            if (other is null || other.Length != Length)
             {
-                if (jsString._value.Length != Length)
-                {
-                    return false;
-                }
-
-                return ToString() == jsString._value;
+                return false;
             }
 
-            return base.Equals(other);
+            return ToString() == other.ToString();
         }
 
         public override int GetHashCode()

+ 1 - 4
Jint/Native/JsSymbol.cs

@@ -16,10 +16,7 @@ public sealed class JsSymbol : JsValue, IEquatable<JsSymbol>
         _value = value;
     }
 
-    public override object ToObject()
-    {
-        return _value;
-    }
+    public override object ToObject() => _value;
 
     /// <summary>
     /// https://tc39.es/ecma262/#sec-symboldescriptivestring

+ 4 - 8
Jint/Native/JsUndefined.cs

@@ -8,15 +8,11 @@ public sealed class JsUndefined : JsValue, IEquatable<JsUndefined>
     {
     }
 
-    public override object ToObject()
-    {
-        return null!;
-    }
+    public override object ToObject() => null!;
 
-    public override string ToString()
-    {
-        return "undefined";
-    }
+    internal override bool ToBoolean() => false;
+
+    public override string ToString() => "undefined";
 
     public override bool IsLooselyEqual(JsValue value)
     {

+ 5 - 0
Jint/Native/JsValue.cs

@@ -146,6 +146,11 @@ namespace Jint.Native
         /// <returns>The underlying CLR value of the <see cref="JsValue"/> instance.</returns>
         public abstract object ToObject();
 
+        /// <summary>
+        /// Coerces boolean value from <see cref="JsValue"/> instance.
+        /// </summary>
+        internal virtual bool ToBoolean() => true;
+
         /// <summary>
         /// Invoke the current value as function.
         /// </summary>

+ 58 - 59
Jint/Native/String/StringPrototype.cs

@@ -178,26 +178,47 @@ namespace Jint.Native.String
             return TrimEndEx(TrimStartEx(s));
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-string.prototype.trim
+        /// </summary>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private JsValue Trim(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
-            var s = TypeConverter.ToString(thisObj);
-            return TrimEx(s);
+            var s = TypeConverter.ToJsString(thisObj);
+            if (s.Length == 0 || (!IsWhiteSpaceEx(s[0]) && !IsWhiteSpaceEx(s[s.Length - 1])))
+            {
+                return s;
+            }
+            return TrimEx(s.ToString());
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-string.prototype.trimstart
+        /// </summary>
         private JsValue TrimStart(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
-            var s = TypeConverter.ToString(thisObj);
-            return TrimStartEx(s);
+            var s = TypeConverter.ToJsString(thisObj);
+            if (s.Length == 0 || !IsWhiteSpaceEx(s[0]))
+            {
+                return s;
+            }
+            return TrimStartEx(s.ToString());
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-string.prototype.trimend
+        /// </summary>
         private JsValue TrimEnd(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
-            var s = TypeConverter.ToString(thisObj);
-            return TrimEndEx(s);
+            var s = TypeConverter.ToJsString(thisObj);
+            if (s.Length == 0 || !IsWhiteSpaceEx(s[s.Length - 1]))
+            {
+                return s;
+            }
+            return TrimEndEx(s.ToString());
         }
 
         private JsValue ToLocaleUpperCase(JsValue thisObj, JsValue[] arguments)
@@ -527,8 +548,7 @@ namespace Jint.Native.String
                 replaceValue = TypeConverter.ToJsString(replaceValue);
             }
 
-            var position = thisString.IndexOf(searchString, StringComparison.Ordinal);
-            var matched = searchString;
+            var position = thisString.IndexOf(searchString);
             if (position < 0)
             {
                 return thisString;
@@ -537,16 +557,16 @@ namespace Jint.Native.String
             string replStr;
             if (functionalReplace)
             {
-                var replValue = ((ICallable) replaceValue).Call(Undefined, matched, position, thisString);
+                var replValue = ((ICallable) replaceValue).Call(Undefined, searchString, position, thisString);
                 replStr = TypeConverter.ToString(replValue);
             }
             else
             {
                 var captures = System.Array.Empty<string>();
-                replStr =  RegExpPrototype.GetSubstitution(matched, thisString.ToString(), position, captures, Undefined, TypeConverter.ToString(replaceValue));
+                replStr =  RegExpPrototype.GetSubstitution(searchString, thisString.ToString(), position, captures, Undefined, TypeConverter.ToString(replaceValue));
             }
 
-            var tailPos = position + matched.Length;
+            var tailPos = position + searchString.Length;
             var newString = thisString.Substring(0, position) + replStr + thisString.Substring(tailPos);
 
             return newString;
@@ -708,11 +728,14 @@ namespace Jint.Native.String
             return string.CompareOrdinal(s.Normalize(NormalizationForm.FormKD), that.Normalize(NormalizationForm.FormKD));
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-string.prototype.lastindexof
+        /// </summary>
         private JsValue LastIndexOf(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
-            var s = TypeConverter.ToString(thisObj);
+            var jsString = TypeConverter.ToJsString(thisObj);
             var searchStr = TypeConverter.ToString(arguments.At(0));
             double numPos = double.NaN;
             if (arguments.Length > 1 && !arguments[1].IsUndefined())
@@ -722,10 +745,16 @@ namespace Jint.Native.String
 
             var pos = double.IsNaN(numPos) ? double.PositiveInfinity : TypeConverter.ToInteger(numPos);
 
-            var len = s.Length;
+            var len = jsString.Length;
             var start = (int)System.Math.Min(System.Math.Max(pos, 0), len);
             var searchLen = searchStr.Length;
 
+            if (searchLen > len)
+            {
+                return JsNumber.IntegerNegativeOne;
+            }
+
+            var s = jsString.ToString();
             var i = start;
             bool found;
 
@@ -736,7 +765,7 @@ namespace Jint.Native.String
 
                 while (found && j < searchLen)
                 {
-                    if ((i + searchLen > len) || (s[i + j] != searchStr[j]))
+                    if (i + searchLen > len || s[i + j] != searchStr[j])
                     {
                         found = false;
                     }
@@ -762,7 +791,7 @@ namespace Jint.Native.String
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             var searchStr = TypeConverter.ToString(arguments.At(0));
             double pos = 0;
             if (arguments.Length > 1 && !arguments[1].IsUndefined())
@@ -772,7 +801,7 @@ namespace Jint.Native.String
 
             if (pos >= s.Length)
             {
-                return -1;
+                return JsNumber.IntegerNegativeOne;
             }
 
             if (pos < 0)
@@ -780,7 +809,7 @@ namespace Jint.Native.String
                 pos = 0;
             }
 
-            return s.IndexOf(searchStr, (int) pos, StringComparison.Ordinal);
+            return s.IndexOf(searchStr, (int) pos);
         }
 
         private JsValue Concat(JsValue thisObj, JsValue[] arguments)
@@ -922,13 +951,13 @@ namespace Jint.Native.String
         }
 
         /// <summary>
-        /// https://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.startswith
+        /// https://tc39.es/ecma262/#sec-string.prototype.startswith
         /// </summary>
         private JsValue StartsWith(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
 
             var searchString = arguments.At(0);
             if (ReferenceEquals(searchString, Null))
@@ -949,31 +978,18 @@ namespace Jint.Native.String
 
             var len = s.Length;
             var start = System.Math.Min(System.Math.Max(pos, 0), len);
-            var searchLength = searchStr.Length;
-            if (searchLength + start > len)
-            {
-                return false;
-            }
-
-            for (var i = 0; i < searchLength; i++)
-            {
-                if (s[start + i] != searchStr[i])
-                {
-                    return false;
-                }
-            }
 
-            return true;
+            return s.StartsWith(searchStr, start);
         }
 
         /// <summary>
-        /// https://www.ecma-international.org/ecma-262/6.0/#sec-string.prototype.endswith
+        /// https://tc39.es/ecma262/#sec-string.prototype.endswith
         /// </summary>
         private JsValue EndsWith(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
-            var s = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
 
             var searchString = arguments.At(0);
             if (ReferenceEquals(searchString, Null))
@@ -993,30 +1009,18 @@ namespace Jint.Native.String
             var len = s.Length;
             var pos = TypeConverter.ToInt32(arguments.At(1, len));
             var end = System.Math.Min(System.Math.Max(pos, 0), len);
-            var searchLength = searchStr.Length;
-            var start = end - searchLength;
 
-            if (start < 0)
-            {
-                return false;
-            }
-
-            for (var i = 0; i < searchLength; i++)
-            {
-                if (s[start + i] != searchStr[i])
-                {
-                    return false;
-                }
-            }
-
-            return true;
+            return s.EndsWith(searchStr, end);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-string.prototype.includes
+        /// </summary>
         private JsValue Includes(JsValue thisObj, JsValue[] arguments)
         {
             TypeConverter.CheckObjectCoercible(Engine, thisObj);
 
-            var s1 = TypeConverter.ToString(thisObj);
+            var s = TypeConverter.ToJsString(thisObj);
             var searchString = arguments.At(0);
 
             if (searchString.IsRegExp())
@@ -1033,12 +1037,7 @@ namespace Jint.Native.String
 
             if (searchStr.Length == 0)
             {
-                return true;
-            }
-
-            if (pos >= s1.Length)
-            {
-                return false;
+                return JsBoolean.True;
             }
 
             if (pos < 0)
@@ -1046,7 +1045,7 @@ namespace Jint.Native.String
                 pos = 0;
             }
 
-            return s1.IndexOf(searchStr, (int) pos, StringComparison.Ordinal) > -1;
+            return s.IndexOf(searchStr, (int) pos) > -1;
         }
 
         private JsValue Normalize(JsValue thisObj, JsValue[] arguments)

+ 0 - 5
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -278,11 +278,6 @@ namespace Jint.Runtime.Interop
             return Equals(obj as ObjectWrapper);
         }
 
-        public override bool Equals(object? obj)
-        {
-            return Equals(obj as ObjectWrapper);
-        }
-
         public bool Equals(ObjectWrapper? other)
         {
             if (ReferenceEquals(null, other))

+ 2 - 24
Jint/Runtime/TypeConverter.cs

@@ -68,7 +68,7 @@ namespace Jint.Runtime
         // how many decimals to check when determining if double is actually an int
         private const double DoubleIsIntegerTolerance = double.Epsilon * 100;
 
-        internal static readonly string[] intToString = new string[1024];
+        private static readonly string[] intToString = new string[1024];
         private static readonly string[] charToString = new string[256];
 
         static TypeConverter()
@@ -172,29 +172,7 @@ namespace Jint.Runtime
         /// <summary>
         /// https://tc39.es/ecma262/#sec-toboolean
         /// </summary>
-        public static bool ToBoolean(JsValue o)
-        {
-            var type = o._type & ~InternalTypes.InternalFlags;
-            switch (type)
-            {
-                case InternalTypes.Boolean:
-                    return ((JsBoolean) o)._value;
-                case InternalTypes.Undefined:
-                case InternalTypes.Null:
-                    return false;
-                case InternalTypes.Integer:
-                    return (int) ((JsNumber) o)._value != 0;
-                case InternalTypes.Number:
-                    var n = ((JsNumber) o)._value;
-                    return n != 0 && !double.IsNaN(n);
-                case InternalTypes.String:
-                    return !((JsString) o).IsNullOrEmpty();
-                case InternalTypes.BigInt:
-                    return ((JsBigInt) o)._value != 0;
-                default:
-                    return true;
-            }
-        }
+        public static bool ToBoolean(JsValue o) => o.ToBoolean();
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-tonumeric