Bladeren bron

Merge branch 'dev' into rel/3.0-beta

# Conflicts:
#	appveyor.yml
Sebastien Ros 4 jaren geleden
bovenliggende
commit
99832aaf3f
100 gewijzigde bestanden met toevoegingen van 4252 en 859 verwijderingen
  1. 0 18
      Common.props
  2. 25 0
      Jint.Benchmark/InteropBenchmark.cs
  3. 2 2
      Jint.Benchmark/Jint.Benchmark.csproj
  4. 1 1
      Jint.Repl/Jint.Repl.csproj
  5. 20 13
      Jint.Repl/Program.cs
  6. 2 2
      Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj
  7. 3 1
      Jint.Tests.Ecma/EcmaTest.cs
  8. 2 2
      Jint.Tests.Ecma/Jint.Tests.Ecma.csproj
  9. 15 0
      Jint.Tests.Test262/BuiltIns/AnnexB/EscapeTests.cs
  10. 15 0
      Jint.Tests.Test262/BuiltIns/AnnexB/UnescapeTests.cs
  11. 15 0
      Jint.Tests.Test262/BuiltIns/JSONTests.cs
  12. 4 2
      Jint.Tests.Test262/Jint.Tests.Test262.csproj
  13. 15 0
      Jint.Tests.Test262/Language/Expressions/ClassTests.cs
  14. 15 0
      Jint.Tests.Test262/Language/Expressions/CoalesceTests.cs
  15. 15 0
      Jint.Tests.Test262/Language/Statements/ClassTests.cs
  16. 64 18
      Jint.Tests.Test262/Test262Test.cs
  17. 58 0
      Jint.Tests.Test262/test/language/expressions/coalesce/abrupt-is-a-short-circuit.js
  18. 31 0
      Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-head-with-logical-and.js
  19. 31 0
      Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-head-with-logical-or.js
  20. 32 0
      Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js
  21. 32 0
      Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js
  22. 53 0
      Jint.Tests.Test262/test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js
  23. 61 0
      Jint.Tests.Test262/test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js
  24. 49 0
      Jint.Tests.Test262/test/language/expressions/coalesce/chainable-with-bitwise-and.js
  25. 49 0
      Jint.Tests.Test262/test/language/expressions/coalesce/chainable-with-bitwise-or.js
  26. 49 0
      Jint.Tests.Test262/test/language/expressions/coalesce/chainable-with-bitwise-xor.js
  27. 50 0
      Jint.Tests.Test262/test/language/expressions/coalesce/chainable.js
  28. 49 0
      Jint.Tests.Test262/test/language/expressions/coalesce/follows-null.js
  29. 49 0
      Jint.Tests.Test262/test/language/expressions/coalesce/follows-undefined.js
  30. 81 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-0.js
  31. 81 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-42.js
  32. 82 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-empty-string.js
  33. 81 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-false.js
  34. 89 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-object.js
  35. 82 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-string.js
  36. 82 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-symbol.js
  37. 81 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-true.js
  38. 56 0
      Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-prevents-evaluation.js
  39. 26 0
      Jint.Tests.Test262/test/language/expressions/coalesce/tco-pos-null.js
  40. 26 0
      Jint.Tests.Test262/test/language/expressions/coalesce/tco-pos-undefined.js
  41. 12 244
      Jint.Tests.Test262/test/skipped.json
  42. 4 2
      Jint.Tests/Jint.Tests.csproj
  43. 1 1
      Jint.Tests/Parser/JavascriptParserTests.cs
  44. 16 0
      Jint.Tests/RunnableInDebugOnlyAttribute.cs
  45. 40 0
      Jint.Tests/Runtime/ArrayTests.cs
  46. 164 0
      Jint.Tests/Runtime/Debugger/BreakPointTests.cs
  47. 223 0
      Jint.Tests/Runtime/Debugger/CallStackTests.cs
  48. 166 0
      Jint.Tests/Runtime/Debugger/ScopeTests.cs
  49. 380 0
      Jint.Tests/Runtime/Debugger/StepModeTests.cs
  50. 56 0
      Jint.Tests/Runtime/Debugger/TestHelpers.cs
  51. 1 1
      Jint.Tests/Runtime/Domain/UuidConstructor.cs
  52. 1 1
      Jint.Tests/Runtime/Domain/UuidInstance.cs
  53. 50 9
      Jint.Tests/Runtime/EngineTests.cs
  54. 52 27
      Jint.Tests/Runtime/ErrorTests.cs
  55. 24 0
      Jint.Tests/Runtime/ExtensionMethods/CustomStringExtensions.cs
  56. 10 0
      Jint.Tests/Runtime/ExtensionMethods/DoubleExtensions.cs
  57. 61 0
      Jint.Tests/Runtime/ExtensionMethods/ExtensionMethodsTest.cs
  58. 33 0
      Jint.Tests/Runtime/ExtensionMethods/FlurlExtensionTest.cs
  59. 12 0
      Jint.Tests/Runtime/ExtensionMethods/PersonExtensions.cs
  60. 44 0
      Jint.Tests/Runtime/FunctionTests.cs
  61. 19 0
      Jint.Tests/Runtime/GlobalTests.cs
  62. 31 0
      Jint.Tests/Runtime/InteropTests.cs
  63. 11 4
      Jint.Tests/Runtime/JsValueConversionTests.cs
  64. 1 0
      Jint/AssemblyInfoExtras.cs
  65. 198 0
      Jint/Collections/RefStack.cs
  66. 14 7
      Jint/Directory.Build.props
  67. 133 39
      Jint/Engine.cs
  68. 62 14
      Jint/EsprimaExtensions.cs
  69. 32 23
      Jint/Extensions/ReflectionExtensions.cs
  70. 2 1
      Jint/Jint.csproj
  71. 0 1
      Jint/Native/Array/ArrayConstructor.cs
  72. 66 1
      Jint/Native/Array/ArrayOperations.cs
  73. 120 74
      Jint/Native/Array/ArrayPrototype.cs
  74. 13 8
      Jint/Native/Boolean/BooleanConstructor.cs
  75. 6 0
      Jint/Native/Boolean/BooleanInstance.cs
  76. 8 5
      Jint/Native/Error/ErrorConstructor.cs
  77. 0 85
      Jint/Native/Function/ArrowFunctionInstance.cs
  78. 22 6
      Jint/Native/Function/BindFunctionInstance.cs
  79. 204 0
      Jint/Native/Function/ClassDefinition.cs
  80. 8 0
      Jint/Native/Function/ConstructorKind.cs
  81. 35 2
      Jint/Native/Function/EvalFunctionInstance.cs
  82. 4 39
      Jint/Native/Function/FunctionConstructor.cs
  83. 105 16
      Jint/Native/Function/FunctionInstance.cs
  84. 18 8
      Jint/Native/Function/FunctionPrototype.cs
  85. 9 0
      Jint/Native/Function/FunctionThisMode.cs
  86. 91 69
      Jint/Native/Function/ScriptFunctionInstance.cs
  87. 4 4
      Jint/Native/Global/GlobalObject.cs
  88. 5 2
      Jint/Native/Iterator/IteratorInstance.cs
  89. 1 1
      Jint/Native/JsNumber.cs
  90. 1 1
      Jint/Native/JsValue.cs
  91. 47 60
      Jint/Native/Json/JsonInstance.cs
  92. 1 1
      Jint/Native/Json/JsonParser.cs
  93. 7 13
      Jint/Native/Json/JsonSerializer.cs
  94. 7 4
      Jint/Native/Map/MapConstructor.cs
  95. 0 1
      Jint/Native/Map/MapPrototype.cs
  96. 12 9
      Jint/Native/Number/NumberConstructor.cs
  97. 6 0
      Jint/Native/Number/NumberInstance.cs
  98. 6 1
      Jint/Native/Object/ObjectConstructor.cs
  99. 19 12
      Jint/Native/Object/ObjectInstance.cs
  100. 1 4
      Jint/Native/Object/ObjectPrototype.cs

+ 0 - 18
Common.props

@@ -1,18 +0,0 @@
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-
-  <PropertyGroup>
-    <Copyright>Sebastien Ros</Copyright>
-    <Authors>Sebastien Ros</Authors>
-    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
-    <DebugType>portable</DebugType>
-    <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
-    <GenerateAssemblyDescriptionAttribute>false</GenerateAssemblyDescriptionAttribute>
-    <GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
-    <GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
-    <GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
-    <GenerateAssemblyCopyrightAttribute>false</GenerateAssemblyCopyrightAttribute>
-    <GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
-    <GenerateAssemblyFileVersionAttribute>true</GenerateAssemblyFileVersionAttribute>
-  </PropertyGroup>
-
-</Project>

+ 25 - 0
Jint.Benchmark/InteropBenchmark.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.IO;
 using System.Reflection;
 using BenchmarkDotNet.Attributes;
 using Jint.Native;
@@ -15,6 +16,7 @@ namespace Jint.Benchmark
         public class Person
         {
             public string Name { get; set; }
+            public int Age { get; set; }
         }
 
         private Engine _engine;
@@ -249,6 +251,29 @@ namespace Jint.Benchmark
                 _engine.Execute("'[email protected]'.split('@');");
             }
         }
+        
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void ResolvingConsoleWriteLine()
+        {
+            var originalOut = Console.Out;
+            Console.SetOut(TextWriter.Null);
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.Execute("System.Console.WriteLine('value to write');");
+            }
+            Console.SetOut(originalOut);
+        }
+        
+        [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
+        public void Setter()
+        {
+            var p = new Person();
+            _engine.SetValue("p", p);
+            for (int i = 0; i < OperationsPerInvoke; ++i)
+            {
+                _engine.Execute("p.Age = 42;");
+            }
+        }
 
         [Benchmark(OperationsPerInvoke = OperationsPerInvoke)]
         public void LoopWithNativeEnumerator()

+ 2 - 2
Jint.Benchmark/Jint.Benchmark.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <AssemblyName>Jint.Benchmark</AssemblyName>
     <OutputType>Exe</OutputType>
     <GenerateAssemblyTitleAttribute>false</GenerateAssemblyTitleAttribute>
@@ -25,7 +25,7 @@
   </ItemGroup>
   <ItemGroup>
     <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
-    <PackageReference Include="Jurassic" Version="3.0.0" />
+    <PackageReference Include="Jurassic" Version="3.1.0" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <PackageReference Include="NiL.JS.NetCore" Version="2.5.1419" />
   </ItemGroup>

+ 1 - 1
Jint.Repl/Jint.Repl.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
     <OutputType>Exe</OutputType>
     <IsPackable>false</IsPackable>
   </PropertyGroup>

+ 20 - 13
Jint.Repl/Program.cs

@@ -2,26 +2,28 @@
 using System.Diagnostics;
 using System.IO;
 using System.Reflection;
+using Esprima;
 using Jint.Native;
 using Jint.Runtime;
 
 namespace Jint.Repl
 {
-    class Program
+    internal static class Program
     {
-        static void Main(string[] args)
+        private static void Main(string[] args)
         {
-
-            var engine = new Engine(cfg => cfg.AllowClr());
+            var engine = new Engine(cfg => cfg
+                .AllowClr()
+            );
 
             engine
                 .SetValue("print", new Action<object>(Console.WriteLine))
                 .SetValue("load", new Func<string, object>(
-                    path => engine.Execute(File.ReadAllText(path))
-                                  .GetCompletionValue()));
+                    path => engine.Execute(File.ReadAllText(path)).GetCompletionValue())
+                );
 
             var filename = args.Length > 0 ? args[0] : "";
-            if (!String.IsNullOrEmpty(filename))
+            if (!string.IsNullOrEmpty(filename))
             {
                 if (!File.Exists(filename))
                 {
@@ -33,9 +35,9 @@ namespace Jint.Repl
                 return;
             }
 
-            Assembly assembly = Assembly.GetExecutingAssembly();
-            FileVersionInfo fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
-            string version = fvi.FileVersion;
+            var assembly = Assembly.GetExecutingAssembly();
+            var fvi = FileVersionInfo.GetVersionInfo(assembly.Location);
+            var version = fvi.FileVersion;
 
             Console.WriteLine("Welcome to Jint ({0})", version);
             Console.WriteLine("Type 'exit' to leave, " +
@@ -44,6 +46,12 @@ namespace Jint.Repl
             Console.WriteLine();
 
             var defaultColor = Console.ForegroundColor;
+            var parserOptions = new ParserOptions("repl")
+            {
+                Tolerant = true,
+                AdaptRegexp = true
+            };
+
             while (true)
             {
                 Console.ForegroundColor = defaultColor;
@@ -56,7 +64,7 @@ namespace Jint.Repl
 
                 try
                 {
-                    var result = engine.GetValue(engine.Execute(input).GetCompletionValue());
+                    var result = engine.GetValue(engine.Execute(input, parserOptions).GetCompletionValue());
                     if (result.Type != Types.None && result.Type != Types.Null && result.Type != Types.Undefined)
                     {
                         var str = TypeConverter.ToString(engine.Json.Stringify(engine.Json, Arguments.From(result, Undefined.Instance, "  ")));
@@ -73,8 +81,7 @@ namespace Jint.Repl
                     Console.ForegroundColor = ConsoleColor.Red;
                     Console.WriteLine(e.Message);
                 }
-
             }
         }
     }
-}
+}

+ 2 - 2
Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks>net461;net5.0</TargetFrameworks>
   </PropertyGroup>
   <ItemGroup>
     <EmbeddedResource Include="Scripts\*.*" />
@@ -9,7 +9,7 @@
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />

+ 3 - 1
Jint.Tests.Ecma/EcmaTest.cs

@@ -197,7 +197,9 @@ namespace Jint.Tests.Ecma
 
             //NOTE: The Date tests in test262 assume the local timezone is Pacific Standard Time
             var pacificTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
-            var engine = new Engine(cfg => cfg.LocalTimeZone(pacificTimeZone));
+            var engine = new Engine(cfg => cfg
+                .LocalTimeZone(pacificTimeZone)
+            );
 
             // loading driver
             if (staSource == null)

+ 2 - 2
Jint.Tests.Ecma/Jint.Tests.Ecma.csproj

@@ -1,12 +1,12 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />

+ 15 - 0
Jint.Tests.Test262/BuiltIns/AnnexB/EscapeTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.BuiltIns.AnnexB
+{
+    public class EscapeTests : Test262Test
+    {
+        [Theory(DisplayName = "annexB\\built-ins\\escape")]
+        [MemberData(nameof(SourceFiles), "annexB\\built-ins\\escape", false)]
+        [MemberData(nameof(SourceFiles), "annexB\\built-ins\\escape", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 15 - 0
Jint.Tests.Test262/BuiltIns/AnnexB/UnescapeTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.BuiltIns.AnnexB
+{
+    public class UnescapeTests : Test262Test
+    {
+        [Theory(DisplayName = "annexB\\built-ins\\unescape")]
+        [MemberData(nameof(SourceFiles), "annexB\\built-ins\\unescape", false)]
+        [MemberData(nameof(SourceFiles), "annexB\\built-ins\\unescape", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 15 - 0
Jint.Tests.Test262/BuiltIns/JSONTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.BuiltIns
+{
+    public class JSONTests : Test262Test
+    {
+        [Theory(DisplayName = "built-ins\\JSON")]
+        [MemberData(nameof(SourceFiles), "built-ins\\JSON", false)]
+        [MemberData(nameof(SourceFiles), "built-ins\\JSON", true, Skip = "Skipped")]
+        protected void RunTest(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 4 - 2
Jint.Tests.Test262/Jint.Tests.Test262.csproj

@@ -1,12 +1,14 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks>net461;net5.0</TargetFrameworks>
+    <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
+    <SignAssembly>true</SignAssembly>
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\Jint\Jint.csproj" />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />

+ 15 - 0
Jint.Tests.Test262/Language/Expressions/ClassTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.Language.Expressions
+{
+    public class ClassTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\expressions\\class")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\class", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\class", true, Skip = "Skipped")]
+        protected void Class(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 15 - 0
Jint.Tests.Test262/Language/Expressions/CoalesceTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.Language.Expressions
+{
+    public class CoalesceTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\expressions\\\\coalesce")]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\\\coalesce", false)]
+        [MemberData(nameof(SourceFiles), "language\\expressions\\\\coalesce", true, Skip = "Skipped")]
+        protected void Addition(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 15 - 0
Jint.Tests.Test262/Language/Statements/ClassTests.cs

@@ -0,0 +1,15 @@
+using Xunit;
+
+namespace Jint.Tests.Test262.Language.Statements
+{
+    public class ClassTests : Test262Test
+    {
+        [Theory(DisplayName = "language\\statements\\class")]
+        [MemberData(nameof(SourceFiles), "language\\statements\\class", false)]
+        [MemberData(nameof(SourceFiles), "language\\statements\\class", true, Skip = "Skipped")]
+        protected void Class(SourceFile sourceFile)
+        {
+            RunTestInternal(sourceFile);
+        }
+    }
+}

+ 64 - 18
Jint.Tests.Test262/Test262Test.cs

@@ -10,8 +10,8 @@ using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
 using Newtonsoft.Json.Linq;
-using Xunit;
 using Xunit.Abstractions;
+using Xunit.Sdk;
 
 namespace Jint.Tests.Test262
 {
@@ -84,8 +84,8 @@ namespace Jint.Tests.Test262
                 .Strict(strict)
             );
 
-            engine.Execute(Sources["sta.js"]);
-            engine.Execute(Sources["assert.js"]);
+            engine.Execute(Sources["sta.js"], CreateParserOptions("sta.js"));
+            engine.Execute(Sources["assert.js"], CreateParserOptions("assert.js"));
             engine.SetValue("print", new ClrFunctionInstance(engine, "print", (thisObj, args) => TypeConverter.ToString(args.At(0))));
 
             var o = engine.Object.Construct(Arguments.Empty);
@@ -100,7 +100,7 @@ namespace Jint.Tests.Test262
                 var parser = new JavaScriptParser(args.At(0).AsString(), options);
                 var script = parser.ParseScript(strict);
 
-                var value = engine.Execute(script).GetCompletionValue();
+                var value = engine.Execute(script, false).GetCompletionValue();
                 
                 return value;
             }), true, true, true));
@@ -112,13 +112,13 @@ namespace Jint.Tests.Test262
                 var files = includes.Groups[1].Captures[0].Value.Split(',');
                 foreach (var file in files)
                 {
-                    engine.Execute(Sources[file.Trim()]);
+                    engine.Execute(Sources[file.Trim()], CreateParserOptions(file.Trim()));
                 }
             }
 
             if (code.IndexOf("propertyHelper.js", StringComparison.OrdinalIgnoreCase) != -1)
             {
-                engine.Execute(Sources["propertyHelper.js"]);
+                engine.Execute(Sources["propertyHelper.js"], CreateParserOptions("propertyHelper.js"));
             }
             
             string lastError = null;
@@ -130,20 +130,16 @@ namespace Jint.Tests.Test262
             }
             catch (JavaScriptException j)
             {
-                lastError = TypeConverter.ToString(j.Error);
+                lastError = j.ToString();
             }
             catch (Exception e)
             {
                 lastError = e.ToString();
             }
 
-            if (negative)
+            if (!negative && !string.IsNullOrWhiteSpace(lastError))
             {
-                Assert.NotNull(lastError);
-            }
-            else
-            {
-                Assert.Null(lastError);
+                throw new XunitException(lastError);
             }
         }
 
@@ -214,10 +210,6 @@ namespace Jint.Tests.Test262
                                 skip = true;
                                 reason = "tail-calls not implemented";
                                 break;
-                            case "class":
-                                skip = true;
-                                reason = "class keyword not implemented";
-                                break;
                             case "BigInt":
                                 skip = true;
                                 reason = "BigInt not implemented";
@@ -234,6 +226,11 @@ namespace Jint.Tests.Test262
                                 skip = true;
                                 reason = "async not implemented";
                                 break;
+                            case "class-fields-private":
+                            case "class-fields-public":
+                                skip = true;
+                                reason = "private/public class fields not implemented in esprima";
+                                break;
                             case "new.target":
                                 skip = true;
                                 reason = "MetaProperty not implemented";
@@ -294,6 +291,48 @@ namespace Jint.Tests.Test262
                     reason = "Unicode support and its special cases need more work";
                 }
 
+                if (name.StartsWith("language/statements/class/subclass/builtin-objects/Promise"))
+                {
+                    skip = true;
+                    reason = "Promise not implemented";
+                }
+                
+                if (name.StartsWith("language/statements/class/subclass/builtin-objects/TypedArray"))
+                {
+                    skip = true;
+                    reason = "TypedArray not implemented";
+                }
+                
+                if (name.StartsWith("language/statements/class/subclass/builtin-objects/WeakMap"))
+                {
+                    skip = true;
+                    reason = "WeakMap not implemented";
+                }
+                
+                if (name.StartsWith("language/statements/class/subclass/builtin-objects/WeakSet"))
+                {
+                    skip = true;
+                    reason = "WeakSet not implemented";
+                }
+                
+                if (name.StartsWith("language/statements/class/subclass/builtin-objects/ArrayBuffer/"))
+                {
+                    skip = true;
+                    reason = "ArrayBuffer not implemented";
+                }
+                
+                if (name.StartsWith("language/statements/class/subclass/builtin-objects/DataView"))
+                {
+                    skip = true;
+                    reason = "DataView not implemented";
+                }
+                                
+                if (name.StartsWith("language/statements/class/subclass/builtins.js"))
+                {
+                    skip = true;
+                    reason = "Uint8Array not implemented";
+                }
+                
                 if (name.StartsWith("built-ins/RegExp/CharacterClassEscapes/"))
                 {
                     skip = true;
@@ -326,8 +365,15 @@ namespace Jint.Tests.Test262
 
             return results;
         }
+        
+        private static ParserOptions CreateParserOptions(string fileName) => 
+            new ParserOptions(fileName)
+            {
+                AdaptRegexp = true,
+                Tolerant = true
+            };
     }
-
+    
     public class SourceFile : IXunitSerializable
     {
         public SourceFile()

+ 58 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/abrupt-is-a-short-circuit.js

@@ -0,0 +1,58 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Abrupt completions are also a Short circuit and prevent evaluation of the right-side expressions
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+function poison() {
+    throw new Test262Error('poison handled');
+}
+
+function morePoison() {
+    throw 'poison!!!!';
+}
+
+x = undefined;
+assert.throws(Test262Error, function() {
+    undefined ?? poison() ?? morePoison();
+}, 'undefined ?? poison() ?? morePoison();');
+
+x = undefined;
+assert.throws(Test262Error, function() {
+    null ?? poison() ?? morePoison();
+}, 'null ?? poison() ?? morePoison();');
+
+assert.throws(Test262Error, function() {
+    poison() ?? morePoison();
+}, 'poison() ?? morePoison();');

+ 31 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-head-with-logical-and.js

@@ -0,0 +1,31 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Cannot immediately contain, or be contained within, an && or || operation.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+features: [coalesce-expression]
+negative:
+    phase: parse
+    type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+0 && 0 ?? true;

+ 31 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-head-with-logical-or.js

@@ -0,0 +1,31 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Cannot immediately contain, or be contained within, an && or || operation.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+features: [coalesce-expression]
+negative:
+    phase: parse
+    type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+0 || 0 ?? true;

+ 32 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-tail-with-logical-and.js

@@ -0,0 +1,32 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    If the CoalesceExpressionHead is undefined or null, follow return the right-side value.
+    Otherwise, return the left-side value.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+features: [coalesce-expression]
+negative:
+    phase: parse
+    type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+0 ?? 0 && true;

+ 32 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/cannot-chain-tail-with-logical-or.js

@@ -0,0 +1,32 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    If the CoalesceExpressionHead is undefined or null, follow return the right-side value.
+    Otherwise, return the left-side value.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+features: [coalesce-expression]
+negative:
+    phase: parse
+    type: SyntaxError
+---*/
+
+$DONOTEVALUATE();
+
+0 ?? 0 || true;

+ 53 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-and.js

@@ -0,0 +1,53 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    CoalesceExpression is chainable with the LogicalANDExpression is any is covered.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = (null ?? 41) && 42;
+assert.sameValue(x, 42, '(null ?? 41) && 42');
+
+x = undefined;
+x = null ?? (41 && 42);
+assert.sameValue(x, 42, 'null ?? (41 && 42)`');
+
+x = undefined;
+x = (41 && 42) ?? null;
+assert.sameValue(x, 42, '(41 && 42) ?? null');
+
+x = undefined;
+x = 41 && (null ?? 42);
+assert.sameValue(x, 42, '41 && (null ?? 42)`');

+ 61 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/chainable-if-parenthesis-covered-logical-or.js

@@ -0,0 +1,61 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    CoalesceExpression is chainable with the LogicalORExpression is any is covered.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = (null ?? 42) || 43;
+assert.sameValue(x, 42, '(null ?? 42) || 43');
+
+x = undefined;
+x = null ?? (42 || 43);
+assert.sameValue(x, 42, 'null ?? (42 || 43)`');
+
+x = undefined;
+x = (null || 42) ?? 43;
+assert.sameValue(x, 42, '(null || 42) ?? 43');
+
+x = undefined;
+x = null || (42 ?? 43);
+assert.sameValue(x, 42, 'null || (42 ?? 43)`');
+
+x = undefined;
+x = (42 || 43) ?? null;
+assert.sameValue(x, 42, '(42 || 43) ?? null');
+
+x = undefined;
+x = 42 || (null ?? 43);
+assert.sameValue(x, 42, '42 || (null ?? 43)');

+ 49 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/chainable-with-bitwise-and.js

@@ -0,0 +1,49 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    CoalesceExpression is chainable with the BitwiseANDExpression
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 42 & 43;
+assert.sameValue(x, 42, 'null ?? 42 & 43');
+
+x = undefined ?? 42 & 43;
+assert.sameValue(x, 42, 'null ?? 42 & 43');
+
+x = false ?? 42 & 43;
+assert.sameValue(x, false, 'false ?? 42 & 43');
+
+x = true ?? 42 & 43;
+assert.sameValue(x, true, 'true ?? 42 & 43');

+ 49 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/chainable-with-bitwise-or.js

@@ -0,0 +1,49 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    CoalesceExpression is chainable with the BitwiseORExpression
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 1 | 42;
+assert.sameValue(x, 43, 'null ?? 1 | 42');
+
+x = undefined ?? 1 | 42;
+assert.sameValue(x, 43, 'null ?? 1 | 42');
+
+x = false ?? 1 | 42;
+assert.sameValue(x, false, 'false ?? 1 | 42');
+
+x = true ?? 1 | 42;
+assert.sameValue(x, true, 'true ?? 1 | 42');

+ 49 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/chainable-with-bitwise-xor.js

@@ -0,0 +1,49 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    CoalesceExpression is chainable with the BitwiseXORExpression
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 1 ^ 42;
+assert.sameValue(x, 43, 'null ?? 1 ^ 42');
+
+x = undefined ?? 1 ^ 42;
+assert.sameValue(x, 43, 'null ?? 1 ^ 42');
+
+x = false ?? 1 ^ 42;
+assert.sameValue(x, false, 'false ?? 1 ^ 42');
+
+x = true ?? 1 ^ 42;
+assert.sameValue(x, true, 'true ?? 1 ^ 42');

+ 50 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/chainable.js

@@ -0,0 +1,50 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    If the CoalesceExpressionHead is undefined or null, follow return the right-side value.
+    Otherwise, return the left-side value.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? undefined ?? 42;
+assert.sameValue(x, 42, 'null ?? undefined ?? 42');
+
+x = undefined ?? null ?? 42;
+assert.sameValue(x, 42, 'undefined ?? null ?? 42');
+
+x = null ?? null ?? 42;
+assert.sameValue(x, 42, 'null ?? null ?? 42');
+
+x = undefined ?? undefined ?? 42;
+assert.sameValue(x, 42, 'null ?? null ?? 42');

+ 49 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/follows-null.js

@@ -0,0 +1,49 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    If the CoalesceExpressionHead is null, follow return the right-side eval.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = null ?? 42;
+assert.sameValue(x, 42, 'null ?? 42');
+
+x = null ?? undefined;
+assert.sameValue(x, undefined, 'null ?? undefined');
+
+x = null ?? null;
+assert.sameValue(x, null, 'null ?? null');
+
+x = null ?? false;
+assert.sameValue(x, false, 'null ?? false');

+ 49 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/follows-undefined.js

@@ -0,0 +1,49 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    If the CoalesceExpressionHead is undefined, follow return the right-side eval.
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined ?? 42;
+assert.sameValue(x, 42, 'undefined ?? 42');
+
+x = undefined ?? undefined;
+assert.sameValue(x, undefined, 'undefined ?? undefined');
+
+x = undefined ?? null;
+assert.sameValue(x, null, 'undefined ?? null');
+
+x = undefined ?? false;
+assert.sameValue(x, false, 'undefined ?? false');

+ 81 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-0.js

@@ -0,0 +1,81 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (0)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = 0 ?? 1;
+assert.sameValue(x, 0, '0 ?? 1');
+
+x = undefined;
+x = 0 ?? null;
+assert.sameValue(x, 0, '0 ?? null');
+
+x = undefined;
+x = 0 ?? undefined;
+assert.sameValue(x, 0, '0 ?? undefined');
+
+x = undefined;
+x = 0 ?? null ?? undefined;
+assert.sameValue(x, 0, '0 ?? null ?? undefined');
+
+x = undefined;
+x = 0 ?? undefined ?? null;
+assert.sameValue(x, 0, '0 ?? undefined ?? null');
+
+x = undefined;
+x = 0 ?? null ?? null;
+assert.sameValue(x, 0, '0 ?? null ?? null');
+
+x = undefined;
+x = 0 ?? undefined ?? undefined;
+assert.sameValue(x, 0, '0 ?? null ?? null');
+
+x = undefined;
+x = null ?? 0 ?? null;
+assert.sameValue(x, 0, 'null ?? 0 ?? null');
+
+x = undefined;
+x = null ?? 0 ?? undefined;
+assert.sameValue(x, 0, 'null ?? 0 ?? undefined');
+
+x = undefined;
+x = undefined ?? 0 ?? null;
+assert.sameValue(x, 0, 'undefined ?? 0 ?? null');
+
+x = undefined;
+x = undefined ?? 0 ?? undefined;
+assert.sameValue(x, 0, 'undefined ?? 0 ?? undefined');

+ 81 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-42.js

@@ -0,0 +1,81 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (42)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = 42 ?? 1;
+assert.sameValue(x, 42, '42 ?? 1');
+
+x = undefined;
+x = 42 ?? null;
+assert.sameValue(x, 42, '42 ?? null');
+
+x = undefined;
+x = 42 ?? undefined;
+assert.sameValue(x, 42, '42 ?? undefined');
+
+x = undefined;
+x = 42 ?? null ?? undefined;
+assert.sameValue(x, 42, '42 ?? null ?? undefined');
+
+x = undefined;
+x = 42 ?? undefined ?? null;
+assert.sameValue(x, 42, '42 ?? undefined ?? null');
+
+x = undefined;
+x = 42 ?? null ?? null;
+assert.sameValue(x, 42, '42 ?? null ?? null');
+
+x = undefined;
+x = 42 ?? undefined ?? undefined;
+assert.sameValue(x, 42, '42 ?? null ?? null');
+
+x = undefined;
+x = null ?? 42 ?? null;
+assert.sameValue(x, 42, 'null ?? 42 ?? null');
+
+x = undefined;
+x = null ?? 42 ?? undefined;
+assert.sameValue(x, 42, 'null ?? 42 ?? undefined');
+
+x = undefined;
+x = undefined ?? 42 ?? null;
+assert.sameValue(x, 42, 'undefined ?? 42 ?? null');
+
+x = undefined;
+x = undefined ?? 42 ?? undefined;
+assert.sameValue(x, 42, 'undefined ?? 42 ?? undefined');

+ 82 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-empty-string.js

@@ -0,0 +1,82 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (the empty string)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var str = '';
+
+x = undefined;
+x = str ?? 1;
+assert.sameValue(x, str, 'str ?? 1');
+
+x = undefined;
+x = str ?? null;
+assert.sameValue(x, str, 'str ?? null');
+
+x = undefined;
+x = str ?? undefined;
+assert.sameValue(x, str, 'str ?? undefined');
+
+x = undefined;
+x = str ?? null ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? undefined');
+
+x = undefined;
+x = str ?? undefined ?? null;
+assert.sameValue(x, str, 'str ?? undefined ?? null');
+
+x = undefined;
+x = str ?? null ?? null;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = str ?? undefined ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = null ?? str ?? null;
+assert.sameValue(x, str, 'null ?? str ?? null');
+
+x = undefined;
+x = null ?? str ?? undefined;
+assert.sameValue(x, str, 'null ?? str ?? undefined');
+
+x = undefined;
+x = undefined ?? str ?? null;
+assert.sameValue(x, str, 'undefined ?? str ?? null');
+
+x = undefined;
+x = undefined ?? str ?? undefined;
+assert.sameValue(x, str, 'undefined ?? str ?? undefined');

+ 81 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-false.js

@@ -0,0 +1,81 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (false)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = false ?? 1;
+assert.sameValue(x, false, 'false ?? 1');
+
+x = undefined;
+x = false ?? null;
+assert.sameValue(x, false, 'false ?? null');
+
+x = undefined;
+x = false ?? undefined;
+assert.sameValue(x, false, 'false ?? undefined');
+
+x = undefined;
+x = false ?? null ?? undefined;
+assert.sameValue(x, false, 'false ?? null ?? undefined');
+
+x = undefined;
+x = false ?? undefined ?? null;
+assert.sameValue(x, false, 'false ?? undefined ?? null');
+
+x = undefined;
+x = false ?? null ?? null;
+assert.sameValue(x, false, 'false ?? null ?? null');
+
+x = undefined;
+x = false ?? undefined ?? undefined;
+assert.sameValue(x, false, 'false ?? null ?? null');
+
+x = undefined;
+x = null ?? false ?? null;
+assert.sameValue(x, false, 'null ?? false ?? null');
+
+x = undefined;
+x = null ?? false ?? undefined;
+assert.sameValue(x, false, 'null ?? false ?? undefined');
+
+x = undefined;
+x = undefined ?? false ?? null;
+assert.sameValue(x, false, 'undefined ?? false ?? null');
+
+x = undefined;
+x = undefined ?? false ?? undefined;
+assert.sameValue(x, false, 'undefined ?? false ?? undefined');

+ 89 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-object.js

@@ -0,0 +1,89 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (object)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var obj = {
+    toString() {
+        return null;
+    },
+    valueOf() {
+        return null;
+    }
+};
+
+x = undefined;
+x = obj ?? 1;
+assert.sameValue(x, obj, 'obj ?? 1');
+
+x = undefined;
+x = obj ?? null;
+assert.sameValue(x, obj, 'obj ?? null');
+
+x = undefined;
+x = obj ?? undefined;
+assert.sameValue(x, obj, 'obj ?? undefined');
+
+x = undefined;
+x = obj ?? null ?? undefined;
+assert.sameValue(x, obj, 'obj ?? null ?? undefined');
+
+x = undefined;
+x = obj ?? undefined ?? null;
+assert.sameValue(x, obj, 'obj ?? undefined ?? null');
+
+x = undefined;
+x = obj ?? null ?? null;
+assert.sameValue(x, obj, 'obj ?? null ?? null');
+
+x = undefined;
+x = obj ?? undefined ?? undefined;
+assert.sameValue(x, obj, 'obj ?? null ?? null');
+
+x = undefined;
+x = null ?? obj ?? null;
+assert.sameValue(x, obj, 'null ?? obj ?? null');
+
+x = undefined;
+x = null ?? obj ?? undefined;
+assert.sameValue(x, obj, 'null ?? obj ?? undefined');
+
+x = undefined;
+x = undefined ?? obj ?? null;
+assert.sameValue(x, obj, 'undefined ?? obj ?? null');
+
+x = undefined;
+x = undefined ?? obj ?? undefined;
+assert.sameValue(x, obj, 'undefined ?? obj ?? undefined');

+ 82 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-string.js

@@ -0,0 +1,82 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (string)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var str = 'undefined';
+
+x = undefined;
+x = str ?? 1;
+assert.sameValue(x, str, 'str ?? 1');
+
+x = undefined;
+x = str ?? null;
+assert.sameValue(x, str, 'str ?? null');
+
+x = undefined;
+x = str ?? undefined;
+assert.sameValue(x, str, 'str ?? undefined');
+
+x = undefined;
+x = str ?? null ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? undefined');
+
+x = undefined;
+x = str ?? undefined ?? null;
+assert.sameValue(x, str, 'str ?? undefined ?? null');
+
+x = undefined;
+x = str ?? null ?? null;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = str ?? undefined ?? undefined;
+assert.sameValue(x, str, 'str ?? null ?? null');
+
+x = undefined;
+x = null ?? str ?? null;
+assert.sameValue(x, str, 'null ?? str ?? null');
+
+x = undefined;
+x = null ?? str ?? undefined;
+assert.sameValue(x, str, 'null ?? str ?? undefined');
+
+x = undefined;
+x = undefined ?? str ?? null;
+assert.sameValue(x, str, 'undefined ?? str ?? null');
+
+x = undefined;
+x = undefined ?? str ?? undefined;
+assert.sameValue(x, str, 'undefined ?? str ?? undefined');

+ 82 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-symbol.js

@@ -0,0 +1,82 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (Symbol)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+var s = Symbol();
+
+x = undefined;
+x = s ?? 1;
+assert.sameValue(x, s, 's ?? null');
+
+x = undefined;
+x = s ?? null;
+assert.sameValue(x, s, 's ?? null');
+
+x = undefined;
+x = s ?? undefined;
+assert.sameValue(x, s, 's ?? undefined');
+
+x = undefined;
+x = s ?? null ?? undefined;
+assert.sameValue(x, s, 's ?? null ?? undefined');
+
+x = undefined;
+x = s ?? undefined ?? null;
+assert.sameValue(x, s, 's ?? undefined ?? null');
+
+x = undefined;
+x = s ?? null ?? null;
+assert.sameValue(x, s, 's ?? null ?? null');
+
+x = undefined;
+x = s ?? undefined ?? undefined;
+assert.sameValue(x, s, 's ?? null ?? null');
+
+x = undefined;
+x = null ?? s ?? null;
+assert.sameValue(x, s, 'null ?? s ?? null');
+
+x = undefined;
+x = null ?? s ?? undefined;
+assert.sameValue(x, s, 'null ?? s ?? undefined');
+
+x = undefined;
+x = undefined ?? s ?? null;
+assert.sameValue(x, s, 'undefined ?? s ?? null');
+
+x = undefined;
+x = undefined ?? s ?? undefined;
+assert.sameValue(x, s, 'undefined ?? s ?? undefined');

+ 81 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-number-true.js

@@ -0,0 +1,81 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit if the CoalesceExpressionHead is not undefined or null (true)
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+
+x = undefined;
+x = true ?? 1;
+assert.sameValue(x, true, 'true ?? null');
+
+x = undefined;
+x = true ?? null;
+assert.sameValue(x, true, 'true ?? null');
+
+x = undefined;
+x = true ?? undefined;
+assert.sameValue(x, true, 'true ?? undefined');
+
+x = undefined;
+x = true ?? null ?? undefined;
+assert.sameValue(x, true, 'true ?? null ?? undefined');
+
+x = undefined;
+x = true ?? undefined ?? null;
+assert.sameValue(x, true, 'true ?? undefined ?? null');
+
+x = undefined;
+x = true ?? null ?? null;
+assert.sameValue(x, true, 'true ?? null ?? null');
+
+x = undefined;
+x = true ?? undefined ?? undefined;
+assert.sameValue(x, true, 'true ?? null ?? null');
+
+x = undefined;
+x = null ?? true ?? null;
+assert.sameValue(x, true, 'null ?? true ?? null');
+
+x = undefined;
+x = null ?? true ?? undefined;
+assert.sameValue(x, true, 'null ?? true ?? undefined');
+
+x = undefined;
+x = undefined ?? true ?? null;
+assert.sameValue(x, true, 'undefined ?? true ?? null');
+
+x = undefined;
+x = undefined ?? true ?? undefined;
+assert.sameValue(x, true, 'undefined ?? true ?? undefined');

+ 56 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/short-circuit-prevents-evaluation.js

@@ -0,0 +1,56 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: >
+    Short circuit can prevent evaluation of the right-side expressions
+esid: sec-conditional-operator
+info: |
+    ConditionalExpression :
+        ShortCircuitExpression
+        ShortCircuitExpression ? AssignmentExpression : AssignmentExpression
+
+    ShortCircuitExpression :
+        LogicalORExpression
+        CoalesceExpression
+
+    CoalesceExpression :
+        CoalesceExpressionHead ?? BitwiseORExpression
+
+    CoalesceExpressionHead :
+        CoalesceExpression
+        BitwiseORExpression
+
+    Runtime Semantics: Evaluation
+
+    CoalesceExpression:CoalesceExpressionHead??BitwiseORExpression
+
+    1. Let lref be the result of evaluating CoalesceExpressionHead.
+    2. Let lval be ? GetValue(lref).
+    3. If lval is undefined or null,
+        a. Let rref be the result of evaluating BitwiseORExpression.
+        b. Return ? GetValue(rref).
+    4. Otherwise, return lval.
+features: [coalesce-expression]
+---*/
+
+var x;
+function poison() {
+    throw new Test262Error('should not evaluate poison');
+}
+
+x = undefined;
+x = undefined ?? 42 ?? undefined ?? poison();
+assert.sameValue(x, 42);
+
+x = undefined;
+x = 42 ?? undefined ?? poison();
+assert.sameValue(x, 42);
+
+x = undefined;
+x = undefined ?? 42 ?? poison();
+assert.sameValue(x, 42);
+
+x = undefined;
+x = 42 ?? poison();
+assert.sameValue(x, 42);

+ 26 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/tco-pos-null.js

@@ -0,0 +1,26 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Expression is a candidate for tail-call optimization.
+esid: sec-static-semantics-hascallintailposition
+info: |
+  Expression Rules
+
+  CoalesceExpression : CoalesceExpressionHead ?? BitwiseORExpression
+
+  1. Return HasCallInTailPosition of BitwiseORExpression with argument call.
+flags: [onlyStrict]
+features: [tail-call-optimization, coalesce-expression]
+includes: [tcoHelper.js]
+---*/
+
+var callCount = 0;
+(function f(n) {
+  if (n === 0) {
+    callCount += 1
+    return;
+  }
+  return null ?? f(n - 1);
+}($MAX_ITERATIONS));
+assert.sameValue(callCount, 1);

+ 26 - 0
Jint.Tests.Test262/test/language/expressions/coalesce/tco-pos-undefined.js

@@ -0,0 +1,26 @@
+// Copyright (C) 2019 Leo Balter. All rights reserved.
+// This code is governed by the BSD license found in the LICENSE file.
+
+/*---
+description: Expression is a candidate for tail-call optimization.
+esid: sec-static-semantics-hascallintailposition
+info: |
+  Expression Rules
+
+  CoalesceExpression : CoalesceExpressionHead ?? BitwiseORExpression
+
+  1. Return HasCallInTailPosition of BitwiseORExpression with argument call.
+flags: [onlyStrict]
+features: [tail-call-optimization, coalesce-expression]
+includes: [tcoHelper.js]
+---*/
+
+var callCount = 0;
+(function f(n) {
+  if (n === 0) {
+    callCount += 1
+    return;
+  }
+  return undefined ?? f(n - 1);
+}($MAX_ITERATIONS));
+assert.sameValue(callCount, 1);

+ 12 - 244
Jint.Tests.Test262/test/skipped.json

@@ -94,6 +94,10 @@
     "source": "language/expressions/object/method-definition/object-method-returns-promise.js",
     "reason": "Promise not implemented"
   },
+  {
+    "source": "language/statements/class/definition/class-method-returns-promise.js",
+    "reason": "Promise not implemented"
+  },
   {
     "source": "built-ins/Symbol/species/subclassing.js",
     "reason": "subclassing not implemented"
@@ -319,249 +323,6 @@
     "reason": "inner binding rejects modification (from parameters) Expected a Error to be thrown but no exception was thrown at all"
   },
 
-  // class support
-  {
-    "source": "built-ins/Function/prototype/toString/class-declaration-complex-heritage.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/class-declaration-explicit-ctor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/class-declaration-implicit-ctor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/class-expression-explicit-ctor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/class-expression-implicit-ctor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/getter-class-expression-static.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/getter-class-expression.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/getter-class-statement-static.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/getter-class-statement.js",
-    "reason": "class not implemented"
-  }, 
-  {
-    "source": "built-ins/Function/prototype/toString/method-class-expression-static.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/method-class-expression.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/method-class-statement-static.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/method-class-statement.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/setter-class-expression-static.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/setter-class-expression.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/setter-class-statement-static.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Function/prototype/toString/setter-class-statement.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/global-code/script-decl-lex.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/global-code/script-decl-lex-deletion.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/global-code/script-decl-var-collision.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/global-code/script-decl-lex-lex.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/global-code/decl-lex.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/let/dstr-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/global-code/decl-lex-deletion.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/let/dstr-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/const/dstr-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/const/dstr-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/dstr-dflt-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "built-ins/Array/prototype/concat/Array.prototype.concat_non-array.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/rest-parameters/with-new-target.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/dstr-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for/dstr-let-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for/dstr-let-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for/dstr-const-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for/dstr-var-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for/dstr-var-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/dstr-dflt-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/dstr-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/lexical-super-property-from-within-constructor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/lexical-super-property.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/arrow-function/lexical-super-call-from-within-constructor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/function/dstr-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/function/dstr-dflt-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/function/dstr-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/function/dstr-dflt-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/dstr-meth-dflt-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/dstr-meth-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/dstr-meth-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/dstr-meth-dflt-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/method-definition/name-invoke-ctor",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/method-definition/name-invoke-ctor.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/expressions/object/method-definition/name-prototype-prop.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for-of/dstr-const-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for-of/dstr-const-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for-of/dstr-let-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for-of/dstr-let-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for-of/dstr-var-ary-ptrn-elem-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-  {
-    "source": "language/statements/for-of/dstr-var-obj-ptrn-id-init-fn-name-class.js",
-    "reason": "class not implemented"
-  },
-
-
   {
     "source": "language/expressions/object/accessor-name-computed-yield-id.js",
     "reason": "accessor / yield not implemented"
@@ -623,7 +384,6 @@
 
 
   {
-    "source": "language/expressions/object/fn-name-arrow.js",
     "source": "language/expressions/arrow-function/scope-paramsbody-var-open.js",
     "reason": "not implemented: Creation of new variable environment for the function body (as distinct from that for the function's parameters)"
   },
@@ -644,6 +404,14 @@
     "source": "built-ins/Object/prototype/toString/proxy-function.js",
     "reason": "generators not implemented"
   },
+  {
+    "source": "language/statements/class/subclass/builtin-objects/GeneratorFunction/instance-prototype.js",
+    "reason": "generators not implemented"
+  },
+  {
+    "source": "language/statements/class/subclass/builtin-objects/GeneratorFunction/regular-subclassing.js",
+    "reason": "generators not implemented"
+  },
 
   // Esprima problems
 

+ 4 - 2
Jint.Tests/Jint.Tests.csproj

@@ -1,6 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <TargetFrameworks>net461;netcoreapp3.1</TargetFrameworks>
+    <TargetFrameworks>net461;net5.0</TargetFrameworks>
     <AssemblyOriginatorKeyFile>..\Jint\Jint.snk</AssemblyOriginatorKeyFile>
     <SignAssembly>true</SignAssembly>
   </PropertyGroup>
@@ -14,7 +14,9 @@
     <Reference Include="Microsoft.CSharp" Condition=" '$(TargetFramework)' == 'net461' " />
   </ItemGroup>
   <ItemGroup>
-    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.7.1" />
+    <PackageReference Include="Flurl.Http.Signed" Version="3.0.0" />
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
+    <PackageReference Include="MongoDB.Bson.signed" Version="2.11.2" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <PackageReference Include="xunit" Version="2.4.1" />
     <PackageReference Include="xunit.analyzers" Version="0.10.0" />

+ 1 - 1
Jint.Tests/Parser/JavascriptParserTests.cs

@@ -159,7 +159,7 @@ namespace Jint.Tests.Parser
 \
 '
 ";
-            var program = new JavaScriptParser(source, new ParserOptions { Loc = true }).ParseScript();
+            var program = new JavaScriptParser(source, new ParserOptions()).ParseScript();
             var expr = program.Body.First().As<ExpressionStatement>().Expression;
             Assert.Equal(1, expr.Location.Start.Line);
             Assert.Equal(0, expr.Location.Start.Column);

+ 16 - 0
Jint.Tests/RunnableInDebugOnlyAttribute.cs

@@ -0,0 +1,16 @@
+using System.Diagnostics;
+using Xunit;
+
+namespace Jint.Tests
+{
+    public class RunnableInDebugOnlyAttribute : FactAttribute
+    {
+        public RunnableInDebugOnlyAttribute()
+        {
+            if (!Debugger.IsAttached)
+            {
+                Skip = "Only running in interactive mode.";
+            }
+        }
+    }
+}

+ 40 - 0
Jint.Tests/Runtime/ArrayTests.cs

@@ -74,5 +74,45 @@ namespace Jint.Tests.Runtime
             var length = (int) array.Length;
             Assert.Equal(0, length);
         }
+
+        [Fact]
+        public void ArraySortIsStable()
+        {
+            const string code = @"
+                var items = [
+                    { name: 'Edward', value: 0 },
+                    { name: 'Sharpe', value: 0 },
+                    { name: 'And', value: 0 },
+                    { name: 'The', value: 1 },
+                    { name: 'Magnetic', value: 0 },
+                    { name: 'Zeros', value: 0 }
+                ];
+
+                // sort by value
+                function compare(a, b) {
+                    return a.value - b.value;
+                }
+
+                var a = items.sort();
+
+                assert(a[0].name == 'Edward');
+                assert(a[1].name == 'Sharpe');
+                assert(a[2].name == 'And');
+                assert(a[3].name == 'The');
+                assert(a[4].name == 'Magnetic');
+                assert(a[5].name == 'Zeros');
+
+                var a = items.sort(compare);
+
+                assert(a[0].name == 'Edward');
+                assert(a[1].name == 'Sharpe');
+                assert(a[2].name == 'And');
+                assert(a[3].name == 'Magnetic');
+                assert(a[4].name == 'Zeros');
+                assert(a[5].name == 'The');
+            ";
+
+            _engine.Execute(code);
+        }
     }
 }

+ 164 - 0
Jint.Tests/Runtime/Debugger/BreakPointTests.cs

@@ -0,0 +1,164 @@
+using Esprima;
+using Jint.Runtime.Debugger;
+using Xunit;
+
+namespace Jint.Tests.Runtime.Debugger
+{
+    public class BreakPointTests
+    {
+       [Fact]
+        public void BreakPointBreaksAtPosition()
+        {
+            string script = @"let x = 1, y = 2;
+if (x === 1)
+{
+x++; y *= 2;
+}";
+
+            var engine = new Engine(options => options.DebugMode());
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                Assert.Equal(4, info.CurrentStatement.Location.Start.Line);
+                Assert.Equal(5, info.CurrentStatement.Location.Start.Column);
+                didBreak = true;
+                return StepMode.None;
+            };
+
+            engine.BreakPoints.Add(new BreakPoint(4, 5));
+            engine.Execute(script);
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void BreakPointBreaksInCorrectSource()
+        {
+            string script1 = @"let x = 1, y = 2;
+if (x === 1)
+{
+x++; y *= 2;
+}";
+
+            string script2 = @"function test(x)
+{
+return x + 2;
+}";
+
+            string script3 = @"const z = 3;
+test(z);";
+
+            var engine = new Engine(options => { options.DebugMode(); });
+            
+            engine.BreakPoints.Add(new BreakPoint("script2", 3, 0));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                Assert.Equal("script2", info.CurrentStatement.Location.Source);
+                Assert.Equal(3, info.CurrentStatement.Location.Start.Line);
+                Assert.Equal(0, info.CurrentStatement.Location.Start.Column);
+                didBreak = true;
+                return StepMode.None;
+            };
+
+            // We need to specify the source to the parser.
+            // And we need locations too (Jint specifies that in its default options)
+            engine.Execute(script1, new ParserOptions("script1"));
+            Assert.False(didBreak);
+
+            engine.Execute(script2, new ParserOptions("script2"));
+            Assert.False(didBreak); 
+
+            // Note that it's actually script3 that executes the function in script2
+            // and triggers the breakpoint
+            engine.Execute(script3, new ParserOptions("script3"));
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void DebuggerStatementTriggersBreak()
+        {
+            string script = @"'dummy';
+debugger;
+'dummy';";
+
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                return StepMode.None;
+            };
+
+            engine.Execute(script);
+
+            Assert.True(didBreak);
+        }
+
+        [Fact(Skip = "Non-source breakpoint is triggered before Statement, while debugger statement is now triggered by ExecuteInternal")]
+        public void DebuggerStatementAndBreakpointTriggerSingleBreak()
+        {
+            string script = @"'dummy';
+debugger;
+'dummy';";
+
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            engine.BreakPoints.Add(new BreakPoint(2, 0));
+
+            int breakTriggered = 0;
+            engine.Break += (sender, info) =>
+            {
+                breakTriggered++;
+                return StepMode.None;
+            };
+
+            engine.Execute(script);
+
+            Assert.Equal(1, breakTriggered);
+        }
+
+        [Fact]
+        public void BreakpointOverridesStepOut()
+        {
+            string script = @"function test()
+{
+'dummy';
+'source';
+'dummy';
+'target';
+}
+test();";
+
+            var engine = new Engine(options => options.DebugMode());
+
+            engine.BreakPoints.Add(new BreakPoint(4, 0));
+            engine.BreakPoints.Add(new BreakPoint(6, 0));
+
+            int step = 0;
+            engine.Break += (sender, info) =>
+            {
+                step++;
+                switch (step)
+                {
+                    case 1:
+                        return StepMode.Out;
+                    case 2:
+                        Assert.True(info.ReachedLiteral("target"));
+                        break;
+                }
+                return StepMode.None;
+            };
+
+            engine.Execute(script);
+
+            Assert.Equal(2, step);
+        }
+    }
+}

+ 223 - 0
Jint.Tests/Runtime/Debugger/CallStackTests.cs

@@ -0,0 +1,223 @@
+using Jint.Runtime.Debugger;
+using Xunit;
+
+namespace Jint.Tests.Runtime.Debugger
+{
+    public class CallStackTests
+    {
+        [Fact]
+        public void NamesRegularFunction()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("regularFunction", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"function regularFunction() { debugger; }
+                regularFunction()");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void NamesFunctionExpression()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("functionExpression", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"const functionExpression = function() { debugger; }
+                functionExpression()");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void NamesNamedFunctionExpression()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("namedFunction", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"const functionExpression = function namedFunction() { debugger; }
+                functionExpression()");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void NamesArrowFunction()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("arrowFunction", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"const arrowFunction = () => { debugger; }
+                arrowFunction()");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void NamesNewFunction()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                // Ideally, this should be "(anonymous)", but FunctionConstructor sets the "anonymous" name.
+                Assert.Equal("anonymous", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"const newFunction = new Function('debugger;');
+                newFunction()");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void NamesMemberFunction()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("memberFunction", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"const obj = { memberFunction() { debugger; } };
+                obj.memberFunction()");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact]
+        public void NamesAnonymousFunction()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("(anonymous)", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"(function()
+                {
+                    debugger;
+                }());");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact(Skip = "Debugger has no accessor awareness yet")]
+        public void NamesGetAccessor()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("get accessor", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"
+                const obj = {
+                    get accessor()
+                    {
+                        debugger;
+                        return 'test';
+                    }
+                };
+                const x = obj.accessor;");
+
+            Assert.True(didBreak);
+        }
+
+        [Fact(Skip = "Debugger has no accessor awareness yet")]
+        public void NamesSetAccessor()
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                Assert.Equal("set accessor", info.CallStack.Peek());
+                return StepMode.None;
+            };
+
+            engine.Execute(
+                @"
+                const obj = {
+                    set accessor(value)
+                    {
+                        debugger;
+                        this.value = value;
+                    }
+                };
+                obj.accessor = 42;");
+
+            Assert.True(didBreak);
+        }
+    }
+}

+ 166 - 0
Jint.Tests/Runtime/Debugger/ScopeTests.cs

@@ -0,0 +1,166 @@
+using Jint.Native;
+using Xunit;
+
+namespace Jint.Tests.Runtime.Debugger
+{
+    public class ScopeTests
+    {
+        [Fact]
+        public void GlobalsAndLocalsIncludeGlobalConst()
+        {
+            string script = @"
+                const globalConstant = 'test';
+                debugger;
+            ";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                var variable = Assert.Single(info.Globals, g => g.Key == "globalConstant");
+                Assert.Equal("test", variable.Value.AsString());
+
+                variable = Assert.Single(info.Locals, g => g.Key == "globalConstant");
+                Assert.Equal("test", variable.Value.AsString());
+            });
+        }
+
+        [Fact]
+        public void GlobalsAndLocalsIncludeGlobalLet()
+        {
+            string script = @"
+                let globalLet = 'test';
+                debugger;";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                var variable = Assert.Single(info.Globals, g => g.Key == "globalLet");
+                Assert.Equal("test", variable.Value.AsString());
+
+                variable = Assert.Single(info.Locals, g => g.Key == "globalLet");
+                Assert.Equal("test", variable.Value.AsString());
+            });
+        }
+
+        [Fact]
+        public void GlobalsAndLocalsIncludeGlobalVar()
+        {
+            string script = @"
+                var globalVar = 'test';
+                debugger;";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                var variable = Assert.Single(info.Globals, g => g.Key == "globalVar");
+                Assert.Equal("test", variable.Value.AsString());
+
+                variable = Assert.Single(info.Locals, g => g.Key == "globalVar");
+                Assert.Equal("test", variable.Value.AsString());
+            });
+        }
+
+        [Fact]
+        public void OnlyLocalsIncludeLocalConst()
+        {
+            string script = @"
+                function test()
+                {
+                    const localConst = 'test';
+                    debugger;
+                }
+                test();";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                var variable = Assert.Single(info.Locals, g => g.Key == "localConst");
+                Assert.Equal("test", variable.Value.AsString());
+                Assert.DoesNotContain(info.Globals, g => g.Key == "localConst");
+            });
+        }
+
+        [Fact]
+        public void OnlyLocalsIncludeLocalLet()
+        {
+            string script = @"
+                function test()
+                {
+                    let localLet = 'test';
+                    debugger;
+                }
+                test();";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                var variable = Assert.Single(info.Locals, g => g.Key == "localLet");
+                Assert.Equal("test", variable.Value.AsString());
+                Assert.DoesNotContain(info.Globals, g => g.Key == "localLet");
+            });
+        }
+
+        [Fact]
+        public void OnlyLocalsIncludeLocalVar()
+        {
+            string script = @"
+                function test()
+                {
+                    var localVar = 'test';
+                    debugger;
+                }
+                test();";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                var variable = Assert.Single(info.Locals, g => g.Key == "localVar");
+                Assert.Equal("test", variable.Value.AsString());
+                Assert.DoesNotContain(info.Globals, g => g.Key == "localVar");
+            });
+        }
+
+        [Fact]
+        public void BlockScopedVariablesAreInvisibleOutsideBlock()
+        {
+            string script = @"
+            debugger;
+            {
+                let blockLet = 'block';
+                const blockConst = 'block';
+            }";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                Assert.DoesNotContain(info.Locals, g => g.Key == "blockLet");
+                Assert.DoesNotContain(info.Locals, g => g.Key == "blockConst");
+            });
+        }
+
+        [Fact]
+        public void BlockScopedConstIsVisibleInsideBlock()
+        {
+            string script = @"
+            'dummy statement';
+            {
+                debugger; // const is initialized (as undefined) at beginning of block
+                const blockConst = 'block';
+            }";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                Assert.Single(info.Locals, c => c.Key == "blockConst" && c.Value == JsUndefined.Undefined);
+            });
+        }
+
+        [Fact]
+        public void BlockScopedLetIsVisibleInsideBlock()
+        {
+            string script = @"
+            'dummy statement';
+            {
+                let blockLet = 'block';
+                debugger; // let isn't initialized until declaration
+            }";
+
+            TestHelpers.TestAtBreak(script, info =>
+            {
+                Assert.Single(info.Locals, v => v.Key == "blockLet" && v.Value.AsString() == "block");
+            });
+        }
+    }
+}

+ 380 - 0
Jint.Tests/Runtime/Debugger/StepModeTests.cs

@@ -0,0 +1,380 @@
+using Jint.Runtime.Debugger;
+using Xunit;
+
+namespace Jint.Tests.Runtime.Debugger
+{
+    public class StepModeTests
+    {
+        /// <summary>
+        /// Helper method to keep tests independent of line numbers, columns or other arbitrary assertions on
+        /// the current statement. Steps through script with StepMode.Into until it reaches literal statement
+        /// (or directive) 'source'. Then counts the steps needed to reach 'target' using the indicated StepMode.
+        /// </summary>
+        /// <param name="script">Script used as basis for test</param>
+        /// <param name="stepMode">StepMode to use from source to target</param>
+        /// <returns>Number of steps from source to target</returns>
+        private int StepsFromSourceToTarget(string script, StepMode stepMode)
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            int steps = 0;
+            bool sourceReached = false;
+            bool targetReached = false;
+            engine.Step += (sender, info) =>
+            {
+                if (sourceReached)
+                {
+                    steps++;
+                    if (info.ReachedLiteral("target"))
+                    {
+                        // Stop stepping
+                        targetReached = true;
+                        return StepMode.None;
+                    }
+                    return stepMode;
+                }
+                else if (info.ReachedLiteral("source"))
+                {
+                    sourceReached = true;
+                    return stepMode;
+                }
+                return StepMode.Into;
+            };
+
+            engine.Execute(script);
+            
+            // Make sure we actually reached the target
+            Assert.True(targetReached);
+
+            return steps;
+        }
+
+        [Fact]
+        public void StepsIntoRegularFunctionCall()
+        {
+            var script = @"
+                'source';
+                test();
+                function test()
+                {
+                    'target';
+                }";
+
+            Assert.Equal(3, StepsFromSourceToTarget(script, StepMode.Into));
+        }
+
+        [Fact]
+        public void StepsOverRegularFunctionCall()
+        {
+            var script = @"
+                'source';
+                test();
+                'target';
+                function test()
+                {
+                    'dummy';
+                }";
+
+            Assert.Equal(2, StepsFromSourceToTarget(script, StepMode.Over));
+        }
+
+        [Fact]
+        public void StepsOutOfRegularFunctionCall()
+        {
+            var script = @"
+                test();
+                'target';
+
+                function test()
+                {
+                    'source';
+                    'dummy';
+                }";
+
+            Assert.Equal(1, StepsFromSourceToTarget(script, StepMode.Out));
+        }
+
+        [Fact]
+        public void StepsIntoMemberFunctionCall()
+        {
+            var script = @"
+                const obj = {
+                    test()
+                    {
+                        'target';
+                    }
+                };
+                'source';
+                obj.test();";
+
+            Assert.Equal(3, StepsFromSourceToTarget(script, StepMode.Into));
+        }
+
+        [Fact]
+        public void StepsOverMemberFunctionCall()
+        {
+            var script = @"
+                const obj = {
+                    test()
+                    {
+                        'dummy';
+                    }
+                };
+                'source';
+                obj.test();
+                'target';";
+
+            Assert.Equal(2, StepsFromSourceToTarget(script, StepMode.Over));
+        }
+
+        [Fact]
+        public void StepsOutOfMemberFunctionCall()
+        {
+            var script = @"
+                const obj = {
+                    test()
+                    {
+                        'source';
+                        'dummy';
+                    }
+                };
+                obj.test();
+                'target';";
+
+            Assert.Equal(1, StepsFromSourceToTarget(script, StepMode.Out));
+        }
+
+        [Fact]
+        public void StepsIntoCallExpression()
+        {
+            var script = @"
+                function test()
+                {
+                    'target';
+                    return 42;
+                }
+                'source';
+                const x = test();";
+
+            Assert.Equal(3, StepsFromSourceToTarget(script, StepMode.Into));
+        }
+
+        [Fact]
+        public void StepsOverCallExpression()
+        {
+            var script = @"
+                function test()
+                {
+                    'dummy';
+                    return 42;
+                }
+                'source';
+                const x = test();
+                'target';";
+
+            Assert.Equal(2, StepsFromSourceToTarget(script, StepMode.Over));
+        }
+
+        [Fact]
+        public void StepsOutOfCallExpression()
+        {
+            var script = @"
+                function test()
+                {
+                    'source';
+                    'dummy';
+                    return 42;
+                }
+                const x = test();
+                'target';";
+
+            Assert.Equal(1, StepsFromSourceToTarget(script, StepMode.Out));
+        }
+
+        [Fact]
+        public void StepsIntoGetAccessor()
+        {
+            var script = @"
+                const obj = {
+                    get test()
+                    {
+                        'target';
+                        return 144;
+                    }
+                };
+                'source';
+                const x = obj.test;";
+
+            Assert.Equal(3, StepsFromSourceToTarget(script, StepMode.Into));
+        }
+
+        [Fact(Skip = "Debugger has no accessor awareness yet")]
+        public void StepsOverGetAccessor()
+        {
+            var script = @"
+                const obj = {
+                    get test()
+                    {
+                        return 144;
+                    }
+                };
+                'source';
+                const x = obj.test;
+                'target';";
+
+            Assert.Equal(2, StepsFromSourceToTarget(script, StepMode.Over));
+        }
+
+        [Fact(Skip = "Debugger has no accessor awareness yet")]
+        public void StepsOutOfGetAccessor()
+        {
+            var script = @"
+                const obj = {
+                    get test()
+                    {
+                        'source';
+                        'dummy';
+                        return 144;
+                    }
+                };
+                const x = obj.test;
+                'target';";
+
+            Assert.Equal(1, StepsFromSourceToTarget(script, StepMode.Out));
+        }
+
+        [Fact]
+        public void StepsIntoSetAccessor()
+        {
+            var script = @"
+                const obj = {
+                    set test(value)
+                    {
+                        'target';
+                        this.value = value;
+                    }
+                };
+                'source';
+                obj.test = 37;";
+
+            Assert.Equal(3, StepsFromSourceToTarget(script, StepMode.Into));
+        }
+
+        [Fact(Skip = "Debugger has no accessor awareness yet")]
+        public void StepsOverSetAccessor()
+        {
+            var script = @"
+                const obj = {
+                    set test(value)
+                    {
+                        this.value = value;
+                    }
+                };
+                'source';
+                obj.test = 37;
+                'target';";
+
+            Assert.Equal(3, StepsFromSourceToTarget(script, StepMode.Over));
+        }
+
+        [Fact(Skip = "Debugger has no accessor awareness yet")]
+        public void StepsOutOfSetAccessor()
+        {
+            var script = @"
+                const obj = {
+                    set test(value)
+                    {
+                        'source';
+                        'dummy';
+                        this.value = value;
+                    }
+                };
+                obj.test = 37;
+                'target';";
+
+            Assert.Equal(1, StepsFromSourceToTarget(script, StepMode.Out));
+        }
+
+        [Fact]
+        public void StepOutOnlyStepsOutOneStackLevel()
+        {
+            var script = @"
+                function test()
+                {
+                    'dummy';
+                    test2();
+                    'target';
+                }
+
+                function test2()
+                {
+                    'source';
+                    'dummy';
+                    'dummy';
+                }
+
+                test();";
+
+            var engine = new Engine(options => options.DebugMode());
+            int step = 0;
+            engine.Step += (sender, info) =>
+            {
+                switch (step)
+                {
+                    case 0:
+                        if (info.ReachedLiteral("source"))
+                        {
+                            step++;
+                            return StepMode.Out;
+                        }
+                        break;
+                    case 1:
+                        Assert.True(info.ReachedLiteral("target"));
+                        step++;
+                        break;
+                }
+                return StepMode.Into;
+            };
+
+            engine.Execute(script);
+        }
+
+        [Fact]
+        public void StepOverDoesSinglestepAfterBreakpoint()
+        {
+            string script = @"
+                test();
+
+                function test()
+                {
+                    'dummy';
+                    debugger;
+                    'target';
+                }";
+
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script));
+
+            bool stepping = false;
+
+            engine.Break += (sender, info) =>
+            {
+                stepping = true;
+                return StepMode.Over;
+            };
+            engine.Step += (sender, info) =>
+            {
+                if (stepping)
+                {
+                    Assert.True(info.ReachedLiteral("target"));
+                }
+                return StepMode.None;
+            };
+
+            engine.Execute(script);
+        }
+    }
+}

+ 56 - 0
Jint.Tests/Runtime/Debugger/TestHelpers.cs

@@ -0,0 +1,56 @@
+using Esprima.Ast;
+using Jint.Runtime.Debugger;
+using System;
+using Xunit;
+
+namespace Jint.Tests.Runtime.Debugger
+{
+    public static class TestHelpers
+    {
+        public static bool IsLiteral(this Statement statement, string requiredValue = null)
+        {
+            switch (statement)
+            {
+                case Directive directive:
+                    return requiredValue == null || directive.Directiv == requiredValue;
+                case ExpressionStatement expr:
+                    return requiredValue == null || (expr.Expression is Literal literal && literal.StringValue == requiredValue);
+            }
+
+            return false;
+        }
+
+        public static bool ReachedLiteral(this DebugInformation info, string requiredValue)
+        {
+            return info.CurrentStatement.IsLiteral(requiredValue);
+        }
+
+        /// <summary>
+        /// Initializes engine in debugmode and executes script until debugger statement,
+        /// before calling stepHandler for assertions. Also asserts that a break was triggered.
+        /// </summary>
+        /// <param name="script">Script that is basis for testing</param>
+        /// <param name="breakHandler">Handler for assertions</param>
+        public static void TestAtBreak(string script, Action<DebugInformation> breakHandler)
+        {
+            var engine = new Engine(options => options
+                .DebugMode()
+                .DebuggerStatementHandling(DebuggerStatementHandling.Script)
+            );
+
+            bool didBreak = false;
+            engine.Break += (sender, info) =>
+            {
+                didBreak = true;
+                breakHandler(info);
+                return StepMode.None;
+            };
+
+            engine.Execute(script);
+
+            Assert.True(didBreak);
+        }
+
+
+    }
+}

+ 1 - 1
Jint.Tests/Runtime/Domain/UuidConstructor.cs

@@ -30,7 +30,7 @@ namespace Jint.Tests.Runtime.Domain
             return Undefined;
         }
 
-        protected override ObjectInstance GetPrototypeOf() => _prototype;
+        protected internal override ObjectInstance GetPrototypeOf() => _prototype;
 
         internal ObjectInstance _prototype;
 

+ 1 - 1
Jint.Tests/Runtime/Domain/UuidInstance.cs

@@ -5,7 +5,7 @@ namespace Jint.Tests.Runtime.Domain
 {
     internal class UuidInstance : ObjectInstance, IObjectWrapper
     {
-        protected override ObjectInstance GetPrototypeOf() => _prototype;
+        protected internal override ObjectInstance GetPrototypeOf() => _prototype;
 
         internal ObjectInstance _prototype;
 

+ 50 - 9
Jint.Tests/Runtime/EngineTests.cs

@@ -937,6 +937,37 @@ namespace Jint.Tests.Runtime
             Assert.Throws<RecursionDepthOverflowException>(() => engine.Execute(input));
         }
 
+        [Fact]
+        public void ShouldLimitRecursionWithAllFunctionInstances()
+        {
+            var engine = new Engine(cfg =>
+            {
+                // Limit recursion to 5 invocations
+                cfg.LimitRecursion(5);
+                cfg.Strict();
+            });
+
+            var ex = Assert.Throws<RecursionDepthOverflowException>(() => engine.Execute(@"
+var myarr = new Array(5000);
+for (var i = 0; i < myarr.length; i++) {
+    myarr[i] = function(i) {
+        myarr[i + 1](i + 1);
+    }
+}
+
+myarr[0](0);
+"));
+        }
+
+        [Fact]
+        public void ShouldLimitRecursionWithGetters()
+        {
+            const string code = @"var obj = { get test() { return this.test + '2';  } }; obj.test;";
+            var engine = new Engine(cfg => cfg.LimitRecursion(10));
+            
+            Assert.Throws<RecursionDepthOverflowException>(() => engine.Execute(code).GetCompletionValue());
+        }
+
         [Fact]
         public void ShouldConvertDoubleToStringWithoutLosingPrecision()
         {
@@ -1185,6 +1216,13 @@ namespace Jint.Tests.Runtime
             ");
         }
 
+        [Fact]
+        public void JsonParserShouldHandleEmptyString()
+        {
+            var ex = Assert.Throws<ParserException>(() => _engine.Execute("JSON.parse('');"));
+            Assert.Equal("Line 1: Unexpected end of input", ex.Message);
+       }
+
         [Fact]
         [ReplaceCulture("fr-FR")]
         public void ShouldBeCultureInvariant()
@@ -1720,9 +1758,10 @@ var prep = function (fn) { fn(); };
             Assert.NotNull(debugInfo.Locals);
 
             Assert.Single(debugInfo.CallStack);
-            Assert.Equal("func1()", debugInfo.CallStack.Peek());
+            Assert.Equal("func1", debugInfo.CallStack.Peek());
             Assert.Contains(debugInfo.Globals, kvp => kvp.Key.Equals("global", StringComparison.Ordinal) && kvp.Value.AsBoolean() == true);
-            Assert.Contains(debugInfo.Globals, kvp => kvp.Key.Equals("local", StringComparison.Ordinal) && kvp.Value.AsBoolean() == false);
+            // Globals no longer contain local variables
+            //Assert.Contains(debugInfo.Globals, kvp => kvp.Key.Equals("local", StringComparison.Ordinal) && kvp.Value.AsBoolean() == false);
             Assert.Contains(debugInfo.Locals, kvp => kvp.Key.Equals("local", StringComparison.Ordinal) && kvp.Value.AsBoolean() == false);
             Assert.DoesNotContain(debugInfo.Locals, kvp => kvp.Key.Equals("global", StringComparison.Ordinal));
 
@@ -2019,15 +2058,17 @@ var prep = function (fn) { fn(); };
             var PDT = TimeZoneInfo.FindSystemTimeZoneById("Pacific Standard Time");
             var FR = new CultureInfo("fr-FR");
 
-            new Engine(options => options.LocalTimeZone(PDT).Culture(FR))
+            var engine = new Engine(options => options.LocalTimeZone(PDT).Culture(FR))
                 .SetValue("log", new Action<object>(Console.WriteLine))
                 .SetValue("assert", new Action<bool>(Assert.True))
-                .SetValue("equal", new Action<object, object>(Assert.Equal))
-                .Execute(@"
-                    var d = new Number(-1.23);
-                    equal('-1.23', d.toString());
-                    equal('-1,23', d.toLocaleString());
-            ");
+                .SetValue("equal", new Action<object, object>(Assert.Equal));
+
+            engine.Execute("var d = new Number(-1.23);");
+            engine.Execute("equal('-1.23', d.toString());");
+            
+            // NET 5 globalization APIs use ICU libraries on newer Windows 10 giving different result
+            // build server is older Windows...
+            engine.Execute("assert('-1,230' === d.toLocaleString() || '-1,23' === d.toLocaleString());");
         }
 
         [Fact]

+ 52 - 27
Jint.Tests/Runtime/ErrorTests.cs

@@ -11,7 +11,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation1()
         {
-            var script = @"
+            const string script = @"
 var a = {};
 
 var b = a.user.name;
@@ -21,34 +21,28 @@ var b = a.user.name;
             var e = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
             Assert.Equal("Cannot read property 'name' of undefined", e.Message);
             Assert.Equal(4, e.Location.Start.Line);
-            Assert.Equal(8, e.Location.Start.Column);
+            Assert.Equal(15, e.Location.Start.Column);
         }
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation1WithoutReferencedName()
         {
-            var script = @"
+            const string script = @"
 var c = a(b().Length);
 ";
 
             var engine = new Engine();
-            engine.SetValue("a", new Action<string>((a) =>
-            {
-
-            }));
-            engine.SetValue("b", new Func<string>(() =>
-            {
-                return null;
-            }));
+            engine.SetValue("a", new Action<string>((_) => { }));
+            engine.SetValue("b", new Func<string>(() => null));
             var e = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
             Assert.Equal("Cannot read property 'Length' of null", e.Message);
             Assert.Equal(2, e.Location.Start.Line);
-            Assert.Equal(10, e.Location.Start.Column);
+            Assert.Equal(14, e.Location.Start.Column);
         }
 
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation2()
         {
-            var script = @"
+            const string script = @"
  test();
 ";
 
@@ -62,26 +56,26 @@ var c = a(b().Length);
         [Fact]
         public void CanProduceCorrectStackTrace()
         {
-            var engine = new Engine(options => options.LimitRecursion(1000));
+            var engine = new Engine();
 
             engine.Execute(@"var a = function(v) {
-	return v.xxx.yyy;
+    return v.xxx.yyy;
 }
 
 var b = function(v) {
 	return a(v);
-}", new ParserOptions("custom.js") { Loc = true });
+}", new ParserOptions("custom.js"));
 
-            var e = Assert.Throws<JavaScriptException>(() => engine.Execute("var x = b(7);", new ParserOptions("main.js") { Loc = true } ));
+            var e = Assert.Throws<JavaScriptException>(() => engine.Execute("var x = b(7);", new ParserOptions("main.js")));
             Assert.Equal("Cannot read property 'yyy' of undefined", e.Message);
             Assert.Equal(2, e.Location.Start.Line);
-            Assert.Equal(8, e.Location.Start.Column);
+            Assert.Equal(17, e.Location.Start.Column);
             Assert.Equal("custom.js", e.Location.Source);
 
-            var stack = e.CallStack;
-            Assert.Equal(@" at a(v) @ custom.js 8:6
- at b(7) @ main.js 8:1
-".Replace("\r\n", "\n"), stack.Replace("\r\n", "\n"));
+            var stack = e.StackTrace;
+            EqualIgnoringNewLineDifferences(@"   at a (v) custom.js:2:18
+   at b (v) custom.js:6:9
+   at main.js:1:9", stack);
         }
 
         private class Folder
@@ -128,11 +122,11 @@ var b = function(v) {
             ));
 
             Assert.Equal("Cannot read property 'Name' of null", javaScriptException.Message);
-            Assert.Equal(@" at recursive(folderInstance.parent) @  31:8
- at recursive(folderInstance.parent) @  31:8
- at recursive(folderInstance.parent) @  31:8
- at recursive(folder) @  16:12
-", javaScriptException.CallStack);
+            EqualIgnoringNewLineDifferences(@"   at recursive (folderInstance) <anonymous>:6:44
+   at recursive (folderInstance) <anonymous>:8:32
+   at recursive (folderInstance) <anonymous>:8:32
+   at recursive (folderInstance) <anonymous>:8:32
+   at <anonymous>:12:17", javaScriptException.StackTrace);
 
             var expected = new List<string>
             {
@@ -140,5 +134,36 @@ var b = function(v) {
             };
             Assert.Equal(expected, recordedFolderTraversalOrder);
         }
+
+        [Fact]
+        public void StackTraceCollectedOnThreeLevels()
+        {
+            var engine = new Engine();
+            const string script = @"var a = function(v) {
+    return v.xxx.yyy;
+}
+
+var b = function(v) {
+    return a(v);
+}
+
+var x = b(7);";
+
+            var ex = Assert.Throws<JavaScriptException>(() => engine.Execute(script));
+
+            const string expected = @"Jint.Runtime.JavaScriptException: Cannot read property 'yyy' of undefined
+   at a (v) <anonymous>:2:18
+   at b (v) <anonymous>:6:12
+   at <anonymous>:9:9";
+            
+            EqualIgnoringNewLineDifferences(expected, ex.ToString());
+        }
+
+        private static void EqualIgnoringNewLineDifferences(string expected, string actual)
+        {
+            expected = expected.Replace("\r\n", "\n");
+            actual = actual.Replace("\r\n", "\n");
+            Assert.Equal(expected, actual);
+        }
     }
 }

+ 24 - 0
Jint.Tests/Runtime/ExtensionMethods/CustomStringExtensions.cs

@@ -0,0 +1,24 @@
+using System.Dynamic;
+using System.Linq;
+using Newtonsoft.Json;
+
+namespace Jint.Tests.Runtime.ExtensionMethods
+{
+    public static class CustomStringExtensions
+    {
+        public static string Backwards(this string value)
+        {
+            return new string(value.Reverse().ToArray());
+        }
+
+        public static T DeserializeObject<T>(this string json)
+        {
+            return JsonConvert.DeserializeObject<T>(json);
+        }
+
+        public static ExpandoObject DeserializeObject(this string json)
+        {
+            return DeserializeObject<ExpandoObject>(json);
+        }
+    }
+}

+ 10 - 0
Jint.Tests/Runtime/ExtensionMethods/DoubleExtensions.cs

@@ -0,0 +1,10 @@
+namespace Jint.Tests.Runtime.ExtensionMethods
+{
+    public static class DoubleExtensions
+    {
+        public static double Add(this double integer, int add)
+        {
+            return integer + add;
+        }
+    }
+}

+ 61 - 0
Jint.Tests/Runtime/ExtensionMethods/ExtensionMethodsTest.cs

@@ -0,0 +1,61 @@
+using Jint.Tests.Runtime.Domain;
+using Xunit;
+
+namespace Jint.Tests.Runtime.ExtensionMethods
+{
+    public class ExtensionMethodsTest
+    {
+        [Fact]
+        public void ShouldInvokeObjectExtensionMethod()
+        {
+            var person = new Person();
+            person.Name = "Mickey Mouse";
+            person.Age = 35;
+
+            var options = new Options();
+            options.AddExtensionMethods(typeof(PersonExtensions));
+
+            var engine = new Engine(options);
+            engine.SetValue("person", person);
+            var age = engine.Execute("person.MultiplyAge(2)").GetCompletionValue().AsInteger();
+
+            Assert.Equal(70, age);
+        }
+
+        [Fact]
+        public void ShouldInvokeStringExtensionMethod()
+        {
+            var options = new Options();
+            options.AddExtensionMethods(typeof(CustomStringExtensions));
+
+            var engine = new Engine(options);
+            var result = engine.Execute("\"Hello World!\".Backwards()").GetCompletionValue().AsString();
+
+            Assert.Equal("!dlroW olleH", result);
+        }
+
+        [Fact]
+        public void ShouldInvokeNumberExtensionMethod()
+        {
+            var options = new Options();
+            options.AddExtensionMethods(typeof(DoubleExtensions));
+
+            var engine = new Engine(options);
+            var result = engine.Execute("let numb = 27; numb.Add(13)").GetCompletionValue().AsInteger();
+
+            Assert.Equal(40, result);
+        }
+
+        [Fact]
+        public void ShouldPrioritizingNonGenericMethod()
+        {
+            var options = new Options();
+            options.AddExtensionMethods(typeof(CustomStringExtensions));
+
+            var engine = new Engine(options);
+            var result = engine.Execute("\"{'name':'Mickey'}\".DeserializeObject()").GetCompletionValue().ToObject() as dynamic;
+
+            Assert.Equal("Mickey", result.name);
+        }
+    }
+}

+ 33 - 0
Jint.Tests/Runtime/ExtensionMethods/FlurlExtensionTest.cs

@@ -0,0 +1,33 @@
+using Flurl.Http;
+
+namespace Jint.Tests.Runtime.ExtensionMethods
+{
+    public class FlurlExtensionTest
+    {
+        [RunnableInDebugOnlyAttribute]
+        public void CanUseFlurlExtensionMethods()
+        {
+            var engine = new Engine(options =>
+            {
+                options.AddExtensionMethods(
+                    typeof(GeneratedExtensions),
+                    typeof(Flurl.GeneratedExtensions));
+            });
+
+            const string script = @"
+var result = 'https://httpbin.org/anything'
+        .AppendPathSegment('person')
+        .SetQueryParams({ a: 1, b: 2 })
+        .WithOAuthBearerToken('my_oauth_token')
+        .PostJsonAsync({
+            first_name: 'Claire',
+            last_name: 'Underwood'
+         }).GetAwaiter().GetResult();
+";
+
+            engine.Execute(script);
+
+            var result = engine.GetValue("result").ToObject();
+        }
+    }
+}

+ 12 - 0
Jint.Tests/Runtime/ExtensionMethods/PersonExtensions.cs

@@ -0,0 +1,12 @@
+using Jint.Tests.Runtime.Domain;
+
+namespace Jint.Tests.Runtime.ExtensionMethods
+{
+    public static class PersonExtensions
+    {
+        public static int MultiplyAge(this Person person, int factor)
+        {
+            return person.Age * factor;
+        }
+    }
+}

+ 44 - 0
Jint.Tests/Runtime/FunctionTests.cs

@@ -0,0 +1,44 @@
+using System;
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class FunctionTests
+    {
+        [Fact]
+        public void BindCombinesBoundArgumentsToCallArgumentsCorrectly()
+        {
+            var e = new Engine();
+            e.Execute("var testFunc = function (a, b, c) { return a + ', ' + b + ', ' + c + ', ' + JSON.stringify(arguments); }");
+            
+            Assert.Equal("a, 1, a, {\"0\":\"a\",\"1\":1,\"2\":\"a\"}", e.Execute("testFunc('a', 1, 'a');").GetCompletionValue().AsString());
+            Assert.Equal("a, 1, a, {\"0\":\"a\",\"1\":1,\"2\":\"a\"}", e.Execute("testFunc.bind('anything')('a', 1, 'a');").GetCompletionValue().AsString());
+        }
+
+        [Fact]
+        public void ProxyCanBeRevokedWithoutContext()
+        {
+            new Engine()
+                .Execute(@"
+                    var revocable = Proxy.revocable({}, {});
+                    var revoke = revocable.revoke;
+                    revoke.call(null);
+                ");
+        }
+
+        [Fact]
+        public void ArrowFunctionShouldBeExtensible()
+        {
+            new Engine()
+                .SetValue("assert", new Action<bool>(Assert.True))
+                .Execute(@"
+                    var a = () => null
+                    Object.defineProperty(a, 'hello', { enumerable: true, get: () => 'world' })
+                    assert(a.hello === 'world')
+
+                    a.foo = 'bar';
+                    assert(a.foo === 'bar');
+                ");
+        }
+    }
+}

+ 19 - 0
Jint.Tests/Runtime/GlobalTests.cs

@@ -0,0 +1,19 @@
+using Xunit;
+
+namespace Jint.Tests.Runtime
+{
+    public class GlobalTests
+    {
+        [Fact]
+        public void UnescapeAtEndOfString()
+        {
+            var e = new Engine();
+
+            Assert.Equal("@", e.Execute("unescape('%40');").GetCompletionValue().AsString());
+            Assert.Equal("@_", e.Execute("unescape('%40_');").GetCompletionValue().AsString());
+            Assert.Equal("@@", e.Execute("unescape('%40%40');").GetCompletionValue().AsString());
+            Assert.Equal("@", e.Execute("unescape('%u0040');").GetCompletionValue().AsString());
+            Assert.Equal("@@", e.Execute("unescape('%u0040%u0040');").GetCompletionValue().AsString());
+        }
+    }
+}

+ 31 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -12,6 +12,7 @@ using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Tests.Runtime.Converters;
 using Jint.Tests.Runtime.Domain;
+using MongoDB.Bson;
 using Newtonsoft.Json.Linq;
 using Shapes;
 using Xunit;
@@ -2287,5 +2288,35 @@ namespace Jint.Tests.Runtime
             Assert.True(_engine.Execute("return o[0] == 'item1'").GetCompletionValue().AsBoolean());
             Assert.True(_engine.Execute("return o[1] == 'item2'").GetCompletionValue().AsBoolean());
         }
+        
+        [Fact]
+        public void DictionaryLikeShouldCheckIndexerAndFallBackToProperty()
+        {
+            const string json = @"{ ""Type"": ""Cat"" }";
+            var jObjectWithTypeProperty = JObject.Parse(json);
+        
+            _engine.SetValue("o", jObjectWithTypeProperty);
+        
+            var typeResult = _engine.Execute("o.Type").GetCompletionValue();
+            
+            // JToken requires conversion
+            Assert.Equal("Cat", TypeConverter.ToString(typeResult));
+
+            // weak equality does conversions from native types
+            Assert.True(_engine.Execute("o.Type == 'Cat'").GetCompletionValue().AsBoolean());
+        }        
+
+        [Fact]
+        public void IndexingBsonProperties()
+        {
+            const string jsonAnimals = @" { ""Animals"": [ { ""Id"": 1, ""Type"": ""Cat"" } ] }";
+            var bsonAnimals = BsonDocument.Parse(jsonAnimals);
+            
+            _engine.SetValue("animals", bsonAnimals["Animals"]);
+
+            // weak equality does conversions from native types
+            Assert.True(_engine.Execute("animals[0].Type == 'Cat'").GetCompletionValue().AsBoolean());
+            Assert.True(_engine.Execute("animals[0].Id == 1").GetCompletionValue().AsBoolean());
+        }
     }
 }

+ 11 - 4
Jint.Tests/Runtime/JsValueConversionTests.cs

@@ -9,10 +9,17 @@ namespace Jint.Tests.Runtime
 {
     public class JsValueConversionTests
     {
+        private Engine _engine;
+
+        public JsValueConversionTests()
+        {
+            _engine = new Engine();
+        }
+
         [Fact]
         public void ShouldBeAnArray()
         {
-            var value = new ArrayInstance(null);
+            var value = new ArrayInstance(_engine);
             Assert.Equal(false, value.IsBoolean());
             Assert.Equal(true, value.IsArray());
             Assert.Equal(false, value.IsDate());
@@ -48,7 +55,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldBeADate()
         {
-            var value = new DateInstance(null);
+            var value = new DateInstance(_engine);
             Assert.Equal(false, value.IsBoolean());
             Assert.Equal(false, value.IsArray());
             Assert.Equal(true, value.IsDate());
@@ -99,7 +106,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldBeAnObject()
         {
-            var value = new ObjectInstance(null);
+            var value = new ObjectInstance(_engine);
             Assert.Equal(false, value.IsBoolean());
             Assert.Equal(false, value.IsArray());
             Assert.Equal(false, value.IsDate());
@@ -116,7 +123,7 @@ namespace Jint.Tests.Runtime
         [Fact]
         public void ShouldBeARegExp()
         {
-            var value = new RegExpInstance(null);
+            var value = new RegExpInstance(_engine);
             Assert.Equal(false, value.IsBoolean());
             Assert.Equal(false, value.IsArray());
             Assert.Equal(false, value.IsDate());

+ 1 - 0
Jint/AssemblyInfoExtras.cs

@@ -1,4 +1,5 @@
 using System.Runtime.CompilerServices;
 
 [assembly: InternalsVisibleTo("Jint.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]
+[assembly: InternalsVisibleTo("Jint.Tests.Test262, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]
 [assembly: InternalsVisibleTo("Jint.Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")]

+ 198 - 0
Jint/Collections/RefStack.cs

@@ -0,0 +1,198 @@
+#nullable enable
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using Jint.Runtime;
+
+namespace Jint.Collections
+{
+    /// <summary>
+    /// Stack for struct types.
+    /// </summary>
+    internal sealed class RefStack<T> : IEnumerable<T> where T : struct
+    {
+        internal T[] _array;
+        internal int _size;
+
+        private const int DefaultCapacity = 2;
+
+        public RefStack(int capacity = DefaultCapacity)
+        {
+            _array = new T[capacity];
+            _size = 0;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref readonly T Peek()
+        {
+            if (_size == 0)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("stack is empty");
+            }
+
+            return ref _array[_size - 1];
+        }
+
+        public T this[int index] => _array[index];
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public bool TryPeek([NotNullWhen(true)] out T item)
+        {
+            if (_size > 0)
+            {
+                item = _array[_size - 1];
+                return true;
+            }
+
+            item = default;
+            return false;
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public ref readonly T Pop()
+        {
+            if (_size == 0)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("stack is empty");
+            }
+
+            _size--;
+            return ref _array[_size];
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public void Push(in T item)
+        {
+            if (_size == _array.Length)
+            {
+                EnsureCapacity(_size + 1);
+            }
+
+            _array[_size++] = item;
+        }
+
+        private void EnsureCapacity(int min)
+        {
+            var array = _array;
+            if (array.Length < min)
+            {
+                var newCapacity = array.Length == 0
+                    ? DefaultCapacity
+                    : array.Length * 2;
+
+                if (newCapacity < min)
+                {
+                    newCapacity = min;
+                }
+
+                Resize(newCapacity);
+            }
+        }
+
+        private void Resize(int value)
+        {
+            if (value != _array.Length)
+            {
+                if (value > 0)
+                {
+                    var newItems = new T[value];
+                    if (_size > 0)
+                    {
+                        Array.Copy(_array, 0, newItems, 0, _size);
+                    }
+
+                    _array = newItems;
+                }
+                else
+                {
+                    _array = Array.Empty<T>();
+                }
+            }
+        }
+
+        public void Clear()
+        {
+            _size = 0;
+        }
+
+        public Enumerator GetEnumerator()
+        {
+            return new Enumerator(this);
+        }
+
+        IEnumerator<T> IEnumerable<T>.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        IEnumerator IEnumerable.GetEnumerator()
+        {
+            return GetEnumerator();
+        }
+
+        internal struct Enumerator : IEnumerator<T>
+        {
+            private readonly RefStack<T> _stack;
+            private int _index;
+            private T? _currentElement;
+
+            internal Enumerator(RefStack<T> stack)
+            {
+                _stack = stack;
+                _index = -2;
+                _currentElement = default;
+            }
+
+            public void Dispose()
+            {
+                _index = -1;
+            }
+
+            public bool MoveNext()
+            {
+                bool returnValue;
+                if (_index == -2)
+                { 
+                    // First call to enumerator.
+                    _index = _stack._size - 1;
+                    returnValue = (_index >= 0);
+                    if (returnValue)
+                    {
+                        _currentElement = _stack._array[_index];
+                    }
+                    return returnValue;
+                }
+
+                if (_index == -1)
+                { 
+                    // End of enumeration.
+                    return false;
+                }
+
+                returnValue = (--_index >= 0);
+                if (returnValue)
+                {
+                    _currentElement = _stack._array[_index];
+                }
+                else
+                {
+                    _currentElement = default;
+                }
+                return returnValue;
+            }
+
+            public T Current => (T) _currentElement!;
+
+            object? IEnumerator.Current => Current;
+
+            void IEnumerator.Reset()
+            {
+                _index = -2;
+                _currentElement = default;
+            }        
+        }
+    }
+}

+ 14 - 7
Jint/Directory.Build.props

@@ -1,29 +1,36 @@
 <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
 
   <PropertyGroup>
+
+    <Copyright>Sebastien Ros</Copyright>
+    <Authors>Sebastien Ros</Authors>
+
     <PackageId>Jint</PackageId>
     <AssemblyTitle>Jint</AssemblyTitle>
-    <Description>Javascript interpreter for .NET which provides full ECMA 5.1 compliance.</Description>
+    <Description>Javascript interpreter for .NET.</Description>
+
     <BuildNumber Condition="'$(BuildNumber)' == ''">0</BuildNumber>
-    <VersionPrefix>3.0.0-beta-$(BuildNumber)</VersionPrefix>
-    <VersionSuffix></VersionSuffix>
+    <VersionPrefix>3.0.0</VersionPrefix>
+    <VersionSuffix>beta-$(BuildNumber)</VersionSuffix>
+    <FileVersion>$(VersionPrefix).$(BuildNumber)</FileVersion>
+
     <PackageId>Jint</PackageId>
-    <PackageTags>javascript, interpreter</PackageTags>
+    <PackageTags>javascript, interpreter, es5, es2015, es6, ecmascript, interop</PackageTags>
     <PackageProjectUrl>https://github.com/sebastienros/jint</PackageProjectUrl>
     <PackageLicenseExpression>BSD-2-Clause</PackageLicenseExpression>
 
     <PublishRepositoryUrl>true</PublishRepositoryUrl>
     <EmbedUntrackedSources>true</EmbedUntrackedSources>
     <IncludeSymbols>true</IncludeSymbols>
+    <DebugType>portable</DebugType>
     <SymbolPackageFormat>snupkg</SymbolPackageFormat>
 
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+
   </PropertyGroup>
 
   <ItemGroup Condition="'$(SourceLinkEnabled)' != 'false'">
     <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
   </ItemGroup>
 
-  <!-- Called after so that the <VersionSuffix> is built after the local ones -->
-  <Import Project="../Common.props"/>
-
 </Project>

+ 133 - 39
Jint/Engine.cs

@@ -28,24 +28,23 @@ using Jint.Runtime;
 using Jint.Runtime.CallStack;
 using Jint.Runtime.Debugger;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Environments;
 using Jint.Runtime.Interop;
+using Jint.Runtime.Interop.Reflection;
 using Jint.Runtime.Interpreter;
+using Jint.Runtime.Interpreter.Expressions;
 using Jint.Runtime.References;
 
 namespace Jint
 {
     public class Engine
     {
-        private static readonly ParserOptions DefaultParserOptions = new ParserOptions
+        private static readonly ParserOptions DefaultParserOptions = new("<anonymous>")
         {
             AdaptRegexp = true,
-            Tolerant = true,
-            Loc = true
+            Tolerant = true
         };
 
-
         private static readonly JsString _errorFunctionName = new JsString("Error");
         private static readonly JsString _evalErrorFunctionName = new JsString("EvalError");
         private static readonly JsString _rangeErrorFunctionName = new JsString("RangeError");
@@ -81,9 +80,9 @@ namespace Jint
         public ITypeConverter ClrTypeConverter { get; internal set; }
 
         // cache of types used when resolving CLR type names
-        internal readonly Dictionary<string, Type> TypeCache = new Dictionary<string, Type>();
+        internal readonly Dictionary<string, Type> TypeCache = new();
 
-        internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new Dictionary<Type, Func<Engine, object, JsValue>>
+        internal static Dictionary<Type, Func<Engine, object, JsValue>> TypeMappers = new()
         {
             { typeof(bool), (engine, v) => (bool) v ? JsBoolean.True : JsBoolean.False },
             { typeof(byte), (engine, v) => JsNumber.Create((byte)v) },
@@ -101,17 +100,16 @@ namespace Jint
             { typeof(UInt16), (engine, v) => JsNumber.Create((UInt16)v) },
             { typeof(UInt32), (engine, v) => JsNumber.Create((UInt32)v) },
             { typeof(UInt64), (engine, v) => JsNumber.Create((UInt64)v) },
-            { typeof(System.Text.RegularExpressions.Regex), (engine, v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "", engine) }
+            { typeof(System.Text.RegularExpressions.Regex), (engine, v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "") }
         };
 
         // shared frozen version
         internal readonly PropertyDescriptor _callerCalleeArgumentsThrowerConfigurable;
         internal readonly PropertyDescriptor _callerCalleeArgumentsThrowerNonConfigurable;
 
-        internal readonly Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>> ClrPropertyDescriptorFactories =
-            new Dictionary<ClrPropertyDescriptorFactoriesKey, Func<Engine, object, PropertyDescriptor>>();
+        internal static Dictionary<ClrPropertyDescriptorFactoriesKey, ReflectionAccessor> ReflectionAccessors = new();
 
-        internal readonly JintCallStack CallStack = new JintCallStack();
+        internal readonly JintCallStack CallStack;
 
         /// <summary>
         /// Constructs a new engine instance.
@@ -171,7 +169,7 @@ namespace Jint
             // their configuration is delayed to a later step
 
             // trigger initialization
-            Global.GetProperty(JsString.Empty);
+            Global.EnsureInitialized();
 
             // this is implementation dependent, and only to pass some unit tests
             Global._prototype = Object.PrototypeObject;
@@ -195,23 +193,13 @@ namespace Jint
             _isStrict = Options.IsStrict;
             _constraints = Options._Constraints;
             _referenceResolver = Options.ReferenceResolver;
+            CallStack = new JintCallStack(Options.MaxRecursionDepth >= 0);
 
             _referencePool = new ReferencePool();
             _argumentsInstancePool = new ArgumentsInstancePool(this);
             _jsValueArrayPool = new JsValueArrayPool();
 
-            if (Options._IsClrAllowed)
-            {
-                Global.SetProperty("System", new PropertyDescriptor(new NamespaceReference(this, "System"), PropertyFlag.AllForbidden));
-                Global.SetProperty("importNamespace", new PropertyDescriptor(new ClrFunctionInstance(
-                    this,
-                    "importNamespace",
-                    (thisObj, arguments) => new NamespaceReference(this, TypeConverter.ToString(arguments.At(0)))), PropertyFlag.AllForbidden));
-            }
-
             Options.Apply(this);
-
-            ClrTypeConverter ??= new DefaultTypeConverter(this);
         }
 
         internal LexicalEnvironment GlobalEnvironment { get; }
@@ -359,19 +347,27 @@ namespace Jint
             return Execute(parser.ParseScript());
         }
 
-        public Engine Execute(Script program)
+        public Engine Execute(Script script)
         {
-            ResetConstraints();
-            ResetLastStatement();
-            ResetCallStack();
+            return Execute(script, true);
+        }
+
+        internal Engine Execute(Script script, bool resetState)
+        {
+            if (resetState)
+            {
+                ResetConstraints();
+                ResetLastStatement();
+                ResetCallStack();
+            }
 
-            using (new StrictModeScope(_isStrict || program.Strict))
+            using (new StrictModeScope(_isStrict || script.Strict))
             {
                 GlobalDeclarationInstantiation(
-                    program,
+                    script,
                     GlobalEnvironment);
 
-                var list = new JintStatementList(this, null, program.Body);
+                var list = new JintStatementList(this, null, script.Body);
                 
                 var result = list.Execute();
                 if (result.Type == CompletionType.Throw)
@@ -467,7 +463,7 @@ namespace Jint
                 if (baseValue.IsObject())
                 {
                     var o = TypeConverter.ToObject(this, baseValue);
-                    var v = o.Get(property);
+                    var v = o.Get(property, reference.GetThisValue());
                     return v;
                 }
                 else
@@ -670,9 +666,9 @@ namespace Jint
         }
 
         /// <summary>
-        /// Gets the last evaluated <see cref="INode"/>.
+        /// Gets the last evaluated <see cref="Node"/>.
         /// </summary>
-        public Node GetLastSyntaxNode()
+        internal Node GetLastSyntaxNode()
         {
             return _lastSyntaxNode;
         }
@@ -684,7 +680,7 @@ namespace Jint
         /// <param name="property">The name of the property to return.</param>
         public JsValue GetValue(JsValue scope, JsValue property)
         {
-            var reference = _referencePool.Rent(scope, property, _isStrict);
+            var reference = _referencePool.Rent(scope, property, _isStrict, thisValue: null);
             var jsValue = GetValue(reference, false);
             _referencePool.Return(reference);
             return jsValue;
@@ -699,20 +695,20 @@ namespace Jint
             return GetIdentifierReference(env, name, StrictModeScope.IsStrictModeCode);
         }
 
-        private Reference GetIdentifierReference(LexicalEnvironment lex, string name, in bool strict)
+        private Reference GetIdentifierReference(LexicalEnvironment env, string name, bool strict)
         {
-            if (lex is null)
+            if (env is null)
             {
                 return new Reference(JsValue.Undefined, name, strict);
             }
 
-            var envRec = lex._record;
+            var envRec = env._record;
             if (envRec.HasBinding(name))
             {
                 return new Reference(envRec, name, strict);
             }
 
-            return GetIdentifierReference(lex._outer, name, strict);
+            return GetIdentifierReference(env._outer, name, strict);
         }
 
         /// <summary>
@@ -795,6 +791,12 @@ namespace Jint
                     for (var j = 0; j < boundNames.Count; j++)
                     {
                         var vn = boundNames[j];
+
+                        if (envRec.HasLexicalDeclaration(vn))
+                        {
+                            ExceptionHelper.ThrowSyntaxError(this, $"Identifier '{vn}' has already been declared");
+                        }
+                        
                         if (!declaredFunctionNames.Contains(vn))
                         {
                             var vnDefinable = envRec.CanDeclareGlobalVar(vn);
@@ -840,7 +842,13 @@ namespace Jint
 
             foreach (var f in functionToInitialize)
             {
-                var fn = f.Id.Name;
+                var fn = f.Id!.Name;
+                
+                if (envRec.HasLexicalDeclaration(fn))
+                {
+                    ExceptionHelper.ThrowSyntaxError(this, $"Identifier '{fn}' has already been declared");
+                }
+                
                 var fo = Function.CreateFunctionObject(f, env);
                 envRec.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false);
             }
@@ -1204,5 +1212,91 @@ namespace Jint
         {
             _executionContexts.ReplaceTopVariableEnvironment(newEnv);
         }
+
+        internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression expression)
+        {
+            if (callable is FunctionInstance functionInstance)
+            {
+                return Call(functionInstance, thisObject, arguments, expression);
+            }
+            
+            return callable.Call(thisObject, arguments);
+        }
+
+        internal JsValue Construct(IConstructor constructor, JsValue[] arguments, JsValue newTarget, JintExpression expression)
+        {
+            if (constructor is FunctionInstance functionInstance)
+            {
+                return Construct(functionInstance, arguments, newTarget, expression);
+            }
+            
+            return constructor.Construct(arguments, newTarget);
+        }
+
+        internal JsValue Call(
+            FunctionInstance functionInstance,
+            JsValue thisObject,
+            JsValue[] arguments,
+            JintExpression expression)
+        {
+            var callStackElement = new CallStackElement(functionInstance, expression);
+            var recursionDepth = CallStack.Push(callStackElement);
+
+            if (recursionDepth > Options.MaxRecursionDepth)
+            {
+                // pop the current element as it was never reached
+                CallStack.Pop();
+                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString());
+            }
+
+            if (_isDebugMode)
+            {
+                DebugHandler.AddToDebugCallStack(functionInstance);
+            }
+
+            var result = functionInstance.Call(thisObject, arguments);
+
+            if (_isDebugMode)
+            {
+                DebugHandler.PopDebugCallStack();
+            }
+
+            CallStack.Pop();
+
+            return result;
+        }
+
+        internal JsValue Construct(
+            FunctionInstance functionInstance,
+            JsValue[] arguments,
+            JsValue newTarget,
+            JintExpression expression)
+        {
+            var callStackElement = new CallStackElement(functionInstance, expression);
+            var recursionDepth = CallStack.Push(callStackElement);
+
+            if (recursionDepth > Options.MaxRecursionDepth)
+            {
+                // pop the current element as it was never reached
+                CallStack.Pop();
+                ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString());
+            }
+
+            if (_isDebugMode)
+            {
+                DebugHandler.AddToDebugCallStack(functionInstance);
+            }
+
+            var result = ((IConstructor) functionInstance).Construct(arguments, newTarget);
+
+            if (_isDebugMode)
+            {
+                DebugHandler.PopDebugCallStack();
+            }
+
+            CallStack.Pop();
+
+            return result;
+        }
     }
 }

+ 62 - 14
Jint/EsprimaExtensions.cs

@@ -1,19 +1,23 @@
+#nullable enable
+
 using System;
 using System.Collections.Generic;
-using System.Globalization;
 using System.Runtime.CompilerServices;
 using Esprima.Ast;
 using Jint.Native;
+using Jint.Native.Function;
+using Jint.Native.Object;
 using Jint.Runtime;
+using Jint.Runtime.Interpreter;
 using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint
 {
     public static class EsprimaExtensions
     {
-        public static JsValue GetKey(this Property property, Engine engine) => GetKey(property.Key, engine, property.Computed);
+        public static JsValue GetKey(this ClassProperty property, Engine engine) => GetKey(property.Key, engine, property.Computed);
 
-        public static JsValue GetKey<T>(this T expression, Engine engine, bool resolveComputed = false) where T : Expression
+        public static JsValue GetKey(this Expression expression, Engine engine, bool resolveComputed = false)
         {
             if (expression is Literal literal)
             {
@@ -40,6 +44,7 @@ namespace Jint
                 || expression.Type == Nodes.CallExpression
                 || expression.Type == Nodes.BinaryExpression
                 || expression.Type == Nodes.UpdateExpression
+                || expression.Type == Nodes.AssignmentExpression
                 || expression is StaticMemberExpression)
             {
                 propertyKey = TypeConverter.ToPropertyKey(JintExpression.Build(engine, expression).GetValue());
@@ -54,7 +59,10 @@ namespace Jint
         internal static bool IsFunctionWithName<T>(this T node) where T : Node
         {
             var type = node.Type;
-            return type == Nodes.FunctionExpression || type == Nodes.ArrowFunctionExpression || type == Nodes.ArrowParameterPlaceHolder;
+            return type == Nodes.FunctionExpression 
+                   || type == Nodes.ArrowFunctionExpression 
+                   || type == Nodes.ArrowParameterPlaceHolder 
+                   || type == Nodes.ClassExpression;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -63,16 +71,10 @@ namespace Jint
             // prevent conversion to scientific notation
             if (literal.Value is double d)
             {
-                return DoubleToString(d);
+                return TypeConverter.ToString(d);
             }
             return literal.Value as string ?? Convert.ToString(literal.Value, provider: null);
         }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal static string DoubleToString(double d)
-        {
-            return (d - (long) d) == 0 ? ((long) d).ToString() : d.ToString(CultureInfo.InvariantCulture);
-        }
         
         internal static void GetBoundNames(this VariableDeclaration variableDeclaration, List<string> target)
         {
@@ -84,7 +86,7 @@ namespace Jint
             }
         }
 
-        internal static void GetBoundNames(this Node parameter, List<string> target)
+        internal static void GetBoundNames(this Node? parameter, List<string> target)
         {
             if (parameter is null || parameter.Type == Nodes.Literal)
             {
@@ -94,7 +96,7 @@ namespace Jint
             // try to get away without a loop
             if (parameter is Identifier id)
             {
-                target.Add(id.Name);
+                target.Add(id.Name!);
                 return;
             }
 
@@ -102,7 +104,7 @@ namespace Jint
             {
                 if (parameter is Identifier identifier)
                 {
-                    target.Add(identifier.Name);
+                    target.Add(identifier.Name!);
                     return;
                 }
                 
@@ -139,10 +141,56 @@ namespace Jint
                 else if (parameter is AssignmentPattern assignmentPattern)
                 {
                     parameter = assignmentPattern.Left;
+                    if (assignmentPattern.Right is ClassExpression classExpression)
+                    {
+                        // TODO check if there's more generic rule
+                        if (classExpression.Id is not null)
+                        {
+                            target.Add(classExpression.Id.Name!);
+                        }
+                    }
                     continue;
                 }
                 break;
             }
         }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-runtime-semantics-definemethod
+        /// </summary>
+        internal static Record DefineMethod(this ClassProperty m, ObjectInstance obj, ObjectInstance? functionPrototype = null)
+        {
+            var engine = obj.Engine;
+            var property = TypeConverter.ToPropertyKey(m.GetKey(engine));
+            var prototype = functionPrototype ?? engine.Function.PrototypeObject;
+            var function = m.Value as IFunction ?? ExceptionHelper.ThrowSyntaxError<IFunction>(engine);
+            var functionDefinition = new JintFunctionDefinition(engine, function);
+            var functionThisMode = functionDefinition.Strict || engine._isStrict
+                ? FunctionThisMode.Strict 
+                : FunctionThisMode.Global;
+
+            var closure = new ScriptFunctionInstance(
+                engine,
+                functionDefinition,
+                engine.ExecutionContext.LexicalEnvironment,
+                functionThisMode,
+                prototype);
+
+            closure.MakeMethod(obj);
+
+            return new Record(property, closure);
+        }
+
+        internal readonly struct Record
+        {
+            public Record(JsValue key, ScriptFunctionInstance closure)
+            {
+                Key = key;
+                Closure = closure;
+            }
+
+            public readonly JsValue Key;
+            public readonly ScriptFunctionInstance Closure;
+        }
     }
 }

+ 32 - 23
Jint/Extensions/ReflectionExtensions.cs

@@ -1,5 +1,8 @@
 using System;
+using System.Collections.Generic;
+using System.Linq;
 using System.Reflection;
+using System.Runtime.CompilerServices;
 
 namespace Jint.Extensions
 {
@@ -7,37 +10,43 @@ namespace Jint.Extensions
     {
         internal static void SetValue(this MemberInfo memberInfo, object forObject, object value)
         {
-            switch (memberInfo.MemberType)
+            if (memberInfo.MemberType == MemberTypes.Field)
             {
-                case MemberTypes.Field:
-                    var fieldInfo = (FieldInfo) memberInfo;
-                    if (value != null && fieldInfo.FieldType.IsAssignableFrom(value.GetType()))
-                    {
-                        fieldInfo.SetValue(forObject, value);
-                    }
-
-                    break;
-                case MemberTypes.Property:
-                    var propertyInfo = (PropertyInfo) memberInfo;
-                    if (value != null && propertyInfo.PropertyType.IsAssignableFrom(value.GetType()))
-                    {
-                        propertyInfo.SetValue(forObject, value);
-                    }
-                    break;
+                var fieldInfo = (FieldInfo) memberInfo;
+                if (value != null && fieldInfo.FieldType.IsInstanceOfType(value))
+                {
+                    fieldInfo.SetValue(forObject, value);
+                }
+            }
+            else if (memberInfo.MemberType == MemberTypes.Property)
+            {
+                var propertyInfo = (PropertyInfo) memberInfo;
+                if (value != null && propertyInfo.PropertyType.IsInstanceOfType(value))
+                {
+                    propertyInfo.SetValue(forObject, value);
+                }
             }
         }
 
         internal static Type GetDefinedType(this MemberInfo memberInfo)
         {
-            switch (memberInfo)
+            return memberInfo switch
             {
-                case PropertyInfo propertyInfo:
-                    return propertyInfo.PropertyType;
-                case FieldInfo fieldInfo:
-                    return fieldInfo.FieldType;
-            }
+                PropertyInfo propertyInfo => propertyInfo.PropertyType,
+                FieldInfo fieldInfo => fieldInfo.FieldType,
+                _ => null
+            };
+        }
 
-            return null;
+        internal static IEnumerable<MethodInfo> GetExtensionMethods(this Type type)
+        {
+            return type.GetMethods(BindingFlags.Public | BindingFlags.Static)
+                .Where(m => m.IsExtensionMethod());
+        }
+
+        private static bool IsExtensionMethod(this MethodBase methodInfo)
+        {
+            return methodInfo.IsDefined(typeof(ExtensionAttribute), true);
         }
     }
 }

+ 2 - 1
Jint/Jint.csproj

@@ -7,6 +7,7 @@
     <LangVersion>latest</LangVersion>
   </PropertyGroup>
   <ItemGroup>
-    <PackageReference Include="Esprima" Version="2.0.0-beta-1328" />
+    <PackageReference Include="Esprima" Version="2.0.0-beta-1338" />
+    <PackageReference Include="Nullable" Version="1.3.0" PrivateAssets="all" />
   </ItemGroup>
 </Project>

+ 0 - 1
Jint/Native/Array/ArrayConstructor.cs

@@ -8,7 +8,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Array

+ 66 - 1
Jint/Native/Array/ArrayOperations.cs

@@ -1,3 +1,5 @@
+using System.Collections;
+using System.Collections.Generic;
 using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Runtime;
@@ -5,7 +7,7 @@ using Jint.Runtime.Descriptors;
 
 namespace Jint.Native.Array
 {
-    internal abstract class ArrayOperations
+    internal abstract class ArrayOperations : IEnumerable<JsValue>
     {
         protected internal const ulong MaxArrayLength = 4294967295;
         protected internal const ulong MaxArrayLikeLength = NumberConstructor.MaxSafeInteger;
@@ -68,6 +70,69 @@ namespace Jint.Native.Array
 
         public abstract void DeletePropertyOrThrow(ulong index);
 
+        internal ArrayLikeIterator GetEnumerator() => new ArrayLikeIterator(this);
+
+        IEnumerator<JsValue> IEnumerable<JsValue>.GetEnumerator() => new ArrayLikeIterator(this);
+
+        IEnumerator IEnumerable.GetEnumerator() => new ArrayLikeIterator(this);
+
+        internal sealed class ArrayLikeIterator : IEnumerator<JsValue>
+        {
+            private readonly ArrayOperations _obj;
+            private ulong _current;
+            private bool _initialized;
+            private readonly uint _length;
+
+            public ArrayLikeIterator(ArrayOperations obj)
+            {
+                _obj = obj;
+                _length = obj.GetLength();
+
+                Reset();
+            }
+
+            public JsValue Current 
+            {
+                get 
+                {
+                    if (!_initialized)
+                    {
+                        return null;
+                    }
+                    else
+                    {
+                        return _obj.TryGetValue(_current, out var temp) ? temp : null;
+                    }
+                }
+            }
+
+            object IEnumerator.Current => Current;
+
+            public void Dispose()
+            {
+            }
+
+            public bool MoveNext()
+            {
+                if (!_initialized)
+                {
+                    _initialized = true;
+                }
+                else
+                {
+                    _current++;
+                }
+
+                return _current < _length;
+            }
+
+            public void Reset()
+            {
+                _initialized = false;
+                _current = 0;
+            }
+        }
+
         private sealed class ObjectInstanceOperations : ArrayOperations<ObjectInstance>
         {
             public ObjectInstanceOperations(ObjectInstance target) : base(target)

+ 120 - 74
Jint/Native/Array/ArrayPrototype.cs

@@ -1,6 +1,8 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
 using Jint.Collections;
+using Jint.Native.Number;
 using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Pooling;
@@ -19,6 +21,7 @@ namespace Jint.Native.Array
     public sealed class ArrayPrototype : ArrayInstance
     {
         private ArrayConstructor _arrayConstructor;
+        internal ClrFunctionInstance _originalIteratorFunction;
 
         private ArrayPrototype(Engine engine) : base(engine)
         {
@@ -89,9 +92,10 @@ namespace Jint.Native.Array
             };
             SetProperties(properties);
 
+            _originalIteratorFunction = new ClrFunctionInstance(Engine, "iterator", Values, 1);
             var symbols = new SymbolDictionary(2)
             {
-                [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "iterator", Values, 1), propertyFlags),
+                [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(_originalIteratorFunction, propertyFlags),
                 [GlobalSymbolRegistry.Unscopables] = new PropertyDescriptor(unscopables, PropertyFlag.Configurable)
             };
             SetSymbols(symbols);
@@ -801,91 +805,30 @@ namespace Jint.Native.Array
             if (len <= 1)
             {
                 return obj.Target;
-            }
+            }            
 
-            int Comparer(JsValue x, JsValue y)
+            // don't eat inner exceptions
+            try
             {
-                if (ReferenceEquals(x, null))
-                {
-                    return 1;
-                }
+                var array = obj.OrderBy(x => x, ArrayComparer.WithFunction(compareFn)).ToArray();
 
-                if (ReferenceEquals(y, null))
+                for (uint i = 0; i < (uint) array.Length; ++i)
                 {
-                    return -1;
-                }
-
-                var xUndefined = x.IsUndefined();
-                var yUndefined = y.IsUndefined();
-                if (xUndefined && yUndefined)
-                {
-                    return 0;
-                }
-
-                if (xUndefined)
-                {
-                    return 1;
-                }
-
-                if (yUndefined)
-                {
-                    return -1;
-                }
-
-                if (compareFn != null)
-                {
-                    var s = TypeConverter.ToNumber(compareFn.Call(Undefined, new[] {x, y}));
-                    if (s < 0)
+                    if (!ReferenceEquals(array[i], null))
                     {
-                        return -1;
+                        obj.Set(i, array[i], updateLength: false, throwOnError: false);
                     }
-
-                    if (s > 0)
+                    else
                     {
-                        return 1;
+                        obj.DeletePropertyOrThrow(i);
                     }
-
-                    return 0;
-                }
-
-                var xString = TypeConverter.ToString(x);
-                var yString = TypeConverter.ToString(y);
-
-                var r = CompareOrdinal(xString, yString);
-                return r;
             }
-
-            var array = new JsValue[len];
-            for (uint i = 0; i < (uint) array.Length; ++i)
-            {
-                var value = obj.TryGetValue(i, out var temp)
-                    ? temp
-                    : null;
-                array[i] = value;
-            }
-
-            // don't eat inner exceptions
-            try
-            {
-                System.Array.Sort(array, Comparer);
             }
             catch (InvalidOperationException e)
             {
                 throw e.InnerException;
             }
 
-            for (uint i = 0; i < (uint) array.Length; ++i)
-            {
-                if (!ReferenceEquals(array[i], null))
-                {
-                    obj.Set(i, array[i], updateLength: false, throwOnError: false);
-                }
-                else
-                {
-                    obj.DeletePropertyOrThrow(i);
-                }
-            }
-
             return obj.Target;
         }
 
@@ -1119,10 +1062,10 @@ namespace Jint.Native.Array
 
             // try to find best capacity
             bool hasObjectSpreadables = false;
-            uint capacity = 0;
+            ulong capacity = 0;
             for (var i = 0; i < items.Count; i++)
             {
-                uint increment;
+                ulong increment;
                 if (!(items[i] is ObjectInstance objectInstance))
                 {
                     increment = 1;
@@ -1131,11 +1074,23 @@ namespace Jint.Native.Array
                 {
                     var isConcatSpreadable = objectInstance.IsConcatSpreadable;
                     hasObjectSpreadables |= isConcatSpreadable;
-                    increment = isConcatSpreadable ? ArrayOperations.For(objectInstance).GetLength() : 1; 
+                    if (isConcatSpreadable)
+                    {
+                        increment = ArrayOperations.For(objectInstance).GetLongLength();
+                    }
+                    else
+                    {
+                        increment = 1;
+                    }
                 }
                 capacity += increment;
             }
 
+            if (capacity > NumberConstructor.MaxSafeInteger)
+            {
+                ExceptionHelper.ThrowTypeError(_engine, "Invalid array length");
+            }
+
             uint n = 0;
             var a = Engine.Array.ArraySpeciesCreate(TypeConverter.ToObject(_engine, thisObj), capacity);
             var aOperations = ArrayOperations.For(a);
@@ -1290,5 +1245,96 @@ namespace Jint.Native.Array
             o.SetLength(len);
             return element;
         }
+
+        private sealed class ArrayComparer : IComparer<JsValue>
+        {
+            /// <summary>
+            /// Default instance without any compare function.
+            /// </summary>
+            public static ArrayComparer Default = new ArrayComparer(null);
+            public static ArrayComparer WithFunction(ICallable compare)
+            {
+                if (compare == null)
+                {
+                    return Default;
+                }
+
+                return new ArrayComparer(compare);
+            }
+
+            private readonly ICallable _compare;
+            private readonly JsValue[] _comparableArray = new JsValue[2];
+
+            private ArrayComparer(ICallable compare)
+            {
+                _compare = compare;
+            }
+
+            public int Compare(JsValue x, JsValue y)
+            {
+                var xIsNull = ReferenceEquals(x, null);
+                var yIsNull = ReferenceEquals(y, null);
+
+                if (xIsNull)
+                {
+                    if (yIsNull)
+                    {
+                        return 0;
+                    }
+                    
+                    return 1;
+                }
+                else
+                {
+                    if (yIsNull)
+                    {
+                        return -1;
+                    }
+                }
+
+                var xUndefined = x.IsUndefined();
+                var yUndefined = y.IsUndefined();
+                if (xUndefined && yUndefined)
+                {
+                    return 0;
+                }
+
+                if (xUndefined)
+                {
+                    return 1;
+                }
+
+                if (yUndefined)
+                {
+                    return -1;
+                }
+
+                if (_compare != null)
+                {
+                    _comparableArray[0] = x;
+                    _comparableArray[1] = y;
+
+                    var s = TypeConverter.ToNumber(_compare.Call(Undefined, _comparableArray));
+                    if (s < 0)
+                    {
+                        return -1;
+                    }
+
+                    if (s > 0)
+                    {
+                        return 1;
+                    }
+
+                    return 0;
+                }
+
+                var xString = TypeConverter.ToString(x);
+                var yString = TypeConverter.ToString(y);
+
+                var r = CompareOrdinal(xString, yString);
+                return r;
+            }
+        }
     }
+
 }

+ 13 - 8
Jint/Native/Boolean/BooleanConstructor.cs

@@ -13,6 +13,8 @@ namespace Jint.Native.Boolean
             : base(engine, _functionName)
         {
         }
+        
+        public BooleanPrototype PrototypeObject { get; private set; }
 
         public static BooleanConstructor CreateBooleanConstructor(Engine engine)
         {
@@ -41,20 +43,23 @@ namespace Jint.Native.Boolean
         }
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.2.1
+        /// https://tc39.es/ecma262/#sec-boolean-constructor-boolean-value
         /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            return Construct(TypeConverter.ToBoolean(arguments.At(0)));
-        }
+            var b = TypeConverter.ToBoolean(arguments.At(0)) 
+                ? JsBoolean.True
+                : JsBoolean.False;
 
-        public BooleanPrototype PrototypeObject { get; private set; }
+            if (newTarget.IsUndefined())
+            {
+                return Construct(b);
+            }
 
-        public BooleanInstance Construct(bool value)
-        {
-            return Construct(value ? JsBoolean.True : JsBoolean.False);
+            var o = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, state) => new BooleanInstance(engine, (JsBoolean) state), b);
+            return Construct(b);
         }
-        
+
         public BooleanInstance Construct(JsBoolean value)
         {
             var instance = new BooleanInstance(Engine)

+ 6 - 0
Jint/Native/Boolean/BooleanInstance.cs

@@ -9,6 +9,12 @@ namespace Jint.Native.Boolean
             : base(engine, ObjectClass.Boolean)
         {
         }
+        
+        public BooleanInstance(Engine engine, JsBoolean value)
+            : base(engine, ObjectClass.Boolean)
+        {
+            PrimitiveValue = value;
+        }
 
         Types IPrimitiveInstance.Type => Types.Boolean;
 

+ 8 - 5
Jint/Native/Error/ErrorConstructor.cs

@@ -45,23 +45,26 @@ namespace Jint.Native.Error
 
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            var instance = new ErrorInstance(Engine, _name);
-            instance._prototype = PrototypeObject;
+            var o = OrdinaryCreateFromConstructor(
+                newTarget,
+                PrototypeObject, 
+                static (e, state) => new ErrorInstance(e, (JsString) state),
+                _name);
 
             var jsValue = arguments.At(0);
             if (!jsValue.IsUndefined())
             {
                 var msg = TypeConverter.ToString(jsValue);
                 var msgDesc = new PropertyDescriptor(msg, true, false, true);
-                instance.DefinePropertyOrThrow("message", msgDesc);
+                o.DefinePropertyOrThrow("message", msgDesc);
             }
 
-            return instance;
+            return o;
         }
 
         public ErrorPrototype PrototypeObject { get; private set; }
 
-        protected override ObjectInstance GetPrototypeOf()
+        protected internal override ObjectInstance GetPrototypeOf()
         {
             return _name._value != "Error" ? _engine.Error : _prototype;
         }

+ 0 - 85
Jint/Native/Function/ArrowFunctionInstance.cs

@@ -1,85 +0,0 @@
-using Esprima.Ast;
-using Jint.Runtime;
-using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
-using Jint.Runtime.Environments;
-using Jint.Runtime.Interpreter;
-
-namespace Jint.Native.Function
-{
-    public sealed class ArrowFunctionInstance : FunctionInstance
-    {
-        private readonly JintFunctionDefinition _function;
-
-        /// <summary>
-        /// http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions
-        /// </summary>
-        public ArrowFunctionInstance(
-            Engine engine,
-            IFunction functionDeclaration,
-            LexicalEnvironment scope,
-            bool strict)
-            : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict)
-        {
-        }
-
-        internal ArrowFunctionInstance(
-            Engine engine,
-            JintFunctionDefinition function,
-            LexicalEnvironment scope,
-            bool strict)
-            : base(engine, function, scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Lexical)
-        {
-            _function = function;
-
-            PreventExtensions();
-            _prototype = Engine.Function.PrototypeObject;
-
-            _length = new LazyPropertyDescriptor(() => JsNumber.Create(function.Initialize(engine, this).Length), PropertyFlag.Configurable);
-        }
-
-        // for example RavenDB wants to inspect this
-        public IFunction FunctionDeclaration => _function.Function;
-
-        /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1
-        /// </summary>
-        public override JsValue Call(JsValue thisArg, JsValue[] arguments)
-        {
-            var strict = Strict || _engine._isStrict;
-            using (new StrictModeScope(strict, true))
-            {
-                var localEnv = LexicalEnvironment.NewFunctionEnvironment(_engine, this, Undefined);
-                _engine.EnterExecutionContext(localEnv, localEnv);
-
-                try
-                {
-                    _engine.FunctionDeclarationInstantiation(
-                        functionInstance: this,
-                        arguments,
-                        localEnv);
-
-                    var result = _function.Execute();
-
-                    var value = result.GetValueOrDefault().Clone();
-
-                    if (result.Type == CompletionType.Throw)
-                    {
-                        ExceptionHelper.ThrowJavaScriptException(_engine, value, result);
-                    }
-
-                    if (result.Type == CompletionType.Return)
-                    {
-                        return value;
-                    }
-                }
-                finally
-                {
-                    _engine.LeaveExecutionContext();
-                }
-
-                return Undefined;
-            }
-        }
-    }
-}

+ 22 - 6
Jint/Native/Function/BindFunctionInstance.cs

@@ -1,5 +1,4 @@
-using System.Linq;
-using Jint.Native.Object;
+using Jint.Native.Object;
 using Jint.Runtime;
 
 namespace Jint.Native.Function
@@ -24,7 +23,11 @@ namespace Jint.Native.Function
                 return ExceptionHelper.ThrowTypeError<ObjectInstance>(Engine);
             }
 
-            return f.Call(BoundThis, CreateArguments(arguments));
+            var args = CreateArguments(arguments);
+            var value = f.Call(BoundThis, args);
+            _engine._jsValueArrayPool.ReturnArray(args);
+
+            return value;
         }
 
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
@@ -34,7 +37,17 @@ namespace Jint.Native.Function
                 return ExceptionHelper.ThrowTypeError<ObjectInstance>(Engine);
             }
 
-            return target.Construct(CreateArguments(arguments), newTarget);
+            var args = CreateArguments(arguments);
+
+            if (ReferenceEquals(this, newTarget))
+            {
+                newTarget = TargetFunction;
+            }
+            
+            var value = target.Construct(args, newTarget);
+            _engine._jsValueArrayPool.ReturnArray(args);
+
+            return value;
         }
 
         public override bool HasInstance(JsValue v)
@@ -49,10 +62,13 @@ namespace Jint.Native.Function
 
         private JsValue[] CreateArguments(JsValue[] arguments)
         {
-            return Enumerable.Union(BoundArgs, arguments).ToArray();
+            var combined = _engine._jsValueArrayPool.RentArray(BoundArgs.Length + arguments.Length);
+            System.Array.Copy(BoundArgs, combined, BoundArgs.Length);
+            System.Array.Copy(arguments, 0, combined, BoundArgs.Length, arguments.Length);
+            return combined;
         }
 
-        internal override bool IsConstructor => TargetFunction is IConstructor;
+        internal override bool IsConstructor => TargetFunction.IsConstructor;
 
         public override string ToString() => "function () { [native code] }";
     }

+ 204 - 0
Jint/Native/Function/ClassDefinition.cs

@@ -0,0 +1,204 @@
+#nullable enable
+
+using Esprima;
+using Esprima.Ast;
+using Jint.Native.Object;
+using Jint.Runtime;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter.Expressions;
+
+namespace Jint.Native.Function
+{
+    internal class ClassDefinition
+    {
+        private static readonly MethodDefinition _superConstructor;
+        private static readonly MethodDefinition _emptyConstructor;
+
+        internal readonly string? _className;
+        private readonly Expression? _superClass;
+        private readonly ClassBody _body;
+
+        static ClassDefinition()
+        {
+            // generate missing constructor AST only once
+            static MethodDefinition CreateConstructorMethodDefinition(string source)
+            {
+                var parser = new JavaScriptParser(source);
+                var script = parser.ParseScript();
+                return (MethodDefinition) script.Body[0].ChildNodes[2].ChildNodes[0];
+            }
+
+            _superConstructor = CreateConstructorMethodDefinition("class temp { constructor(...args) { super(...args); } }");
+            _emptyConstructor = CreateConstructorMethodDefinition("class temp { constructor() {} }");
+        }
+
+        public ClassDefinition(
+            Identifier? className,
+            Expression? superClass,
+            ClassBody body)
+        {
+            _className = className?.Name;
+            _superClass = superClass;
+            _body = body;
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation
+        /// </summary>
+        public ScriptFunctionInstance BuildConstructor(
+            Engine engine,
+            LexicalEnvironment env)
+        {
+            // A class definition is always strict mode code.
+            using var _ = (new StrictModeScope(true, true));
+            
+            var classScope = LexicalEnvironment.NewDeclarativeEnvironment(engine, env);
+
+            if (_className is not null)
+            {
+                classScope._record.CreateImmutableBinding(_className, true);
+            }
+
+            ObjectInstance? protoParent = null;
+            ObjectInstance? constructorParent = null;
+            if (_superClass is null)
+            {
+                protoParent = engine.Object.PrototypeObject;
+                constructorParent = engine.Function.PrototypeObject;
+            }
+            else
+            {
+                engine.UpdateLexicalEnvironment(classScope);
+                var superclass = JintExpression.Build(engine, _superClass).GetValue();
+                engine.UpdateLexicalEnvironment(env);
+
+                if (superclass.IsNull())
+                {
+                    protoParent = null;
+                    constructorParent = engine.Function.PrototypeObject;
+                }
+                else if (!superclass.IsConstructor)
+                {
+                    ExceptionHelper.ThrowTypeError(engine, "super class is not a constructor");
+                }
+                else
+                {
+                    var temp = superclass.Get("prototype");
+                    if (temp is ObjectInstance protoParentObject)
+                    {
+                        protoParent = protoParentObject;
+                    }
+                    else if (temp._type == InternalTypes.Null)
+                    {
+                        // OK
+                    }
+                    else
+                    {
+                        ExceptionHelper.ThrowTypeError(engine);
+                        return null!;
+                    }
+
+                    constructorParent = (ObjectInstance) superclass;
+                }
+            }
+
+            var proto = new ObjectInstance(engine)
+            {
+                _prototype = protoParent
+            };
+
+            MethodDefinition? constructor = null;
+            var classBody = _body.Body;
+            for (var i = 0; i < classBody.Count; ++i)
+            {
+                if (classBody[i].Kind == PropertyKind.Constructor)
+                {
+                    constructor = (MethodDefinition) classBody[i];
+                    break;
+                }
+            }
+
+            constructor ??= _superClass != null
+                ? _superConstructor
+                : _emptyConstructor;
+
+            engine.UpdateLexicalEnvironment(classScope);
+
+            ScriptFunctionInstance F;
+            try
+            {
+                var constructorInfo = constructor.DefineMethod(proto, constructorParent);
+                F = constructorInfo.Closure;
+                if (_className is not null)
+                {
+                    F.SetFunctionName(_className);
+                }
+
+                F.MakeConstructor(false, proto);
+                F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived;
+                F.MakeClassConstructor();
+                proto.CreateMethodProperty(CommonProperties.Constructor, F);
+
+                foreach (var classProperty in _body.Body)
+                {
+                    if (classProperty is not MethodDefinition m || m.Kind == PropertyKind.Constructor)
+                    {
+                        continue;
+                    }
+
+                    var target = !m.Static ? proto : F;
+                    PropertyDefinitionEvaluation(engine, target, m);
+                }
+            }
+            finally
+            {
+                engine.UpdateLexicalEnvironment(env);
+            }
+
+            if (_className is not null)
+            {
+                classScope._record.InitializeBinding(_className, F);
+            }
+
+            return F;
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation
+        /// </summary>
+        private static void PropertyDefinitionEvaluation(
+            Engine engine,
+            ObjectInstance obj,
+            MethodDefinition method)
+        {
+            if (method.Kind != PropertyKind.Get && method.Kind != PropertyKind.Set)
+            {
+                var methodDef = method.DefineMethod(obj);
+                methodDef.Closure.SetFunctionName(methodDef.Key);
+                var desc = new PropertyDescriptor(methodDef.Closure, PropertyFlag.NonEnumerable);
+                obj.DefinePropertyOrThrow(methodDef.Key, desc);
+            }
+            else
+            {
+                var propKey = TypeConverter.ToPropertyKey(method.GetKey(engine));
+                var function = method.Value as IFunction ?? ExceptionHelper.ThrowSyntaxError<IFunction>(obj.Engine);
+
+                var closure = new ScriptFunctionInstance(
+                    obj.Engine,
+                    function,
+                    obj.Engine.ExecutionContext.LexicalEnvironment,
+                    true);
+                closure.SetFunctionName(propKey, method.Kind == PropertyKind.Get ? "get" : "set");
+                closure.MakeMethod(obj);
+
+                var propDesc = new GetSetPropertyDescriptor(
+                    method.Kind == PropertyKind.Get ? closure : null,
+                    method.Kind == PropertyKind.Set ? closure : null,
+                    PropertyFlag.Configurable);
+
+                obj.DefinePropertyOrThrow(propKey, propDesc);
+            }
+        }
+    }
+}

+ 8 - 0
Jint/Native/Function/ConstructorKind.cs

@@ -0,0 +1,8 @@
+namespace Jint.Native.Function
+{
+    internal enum ConstructorKind
+    {
+        Base,
+        Derived
+    }
+}

+ 35 - 2
Jint/Native/Function/EvalFunctionInstance.cs

@@ -21,19 +21,39 @@ namespace Jint.Native.Function
 
         public override JsValue Call(JsValue thisObject, JsValue[] arguments)
         {
-            return Call(thisObject, arguments, false);
+            return PerformEval(arguments, false);
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-performeval
         /// </summary>
-        public JsValue Call(JsValue thisObject, JsValue[] arguments, bool direct)
+        public JsValue PerformEval(JsValue[] arguments, bool direct)
         {
             if (!(arguments.At(0) is JsString x))
             {
                 return arguments.At(0);
             }
 
+            var inFunction = false;
+            var inMethod = false;
+            var inDerivedConstructor = false;
+
+            if (direct)
+            {
+                var thisEnvRec = _engine.GetThisEnvironment();
+                if (thisEnvRec is FunctionEnvironmentRecord functionEnvironmentRecord)
+                {
+                    var F = functionEnvironmentRecord._functionObject;
+                    inFunction = true;
+                    inMethod = thisEnvRec.HasSuperBinding();
+                    
+                    if (F._constructorKind == ConstructorKind.Derived)
+                    {
+                        inDerivedConstructor = true;
+                    }
+                }
+            }
+
             var parser = new JavaScriptParser(x.ToString(), ParserOptions);
             Script script;
             try
@@ -53,6 +73,19 @@ namespace Jint.Native.Function
                 return Undefined;
             }
 
+            if (!inFunction)
+            {
+                // if body Contains NewTarget, throw a SyntaxError exception.
+            } 
+            if (!inMethod)
+            {
+                // if body Contains SuperProperty, throw a SyntaxError exception.
+            } 
+            if (!inDerivedConstructor)
+            {
+                // if body Contains SuperCall, throw a SyntaxError exception.
+            } 
+
             var strictEval = script.Strict || _engine._isStrict;
             var ctx = _engine.ExecutionContext;
 

+ 4 - 39
Jint/Native/Function/FunctionConstructor.cs

@@ -113,6 +113,8 @@ namespace Jint.Native.Function
                 function,
                 _engine.GlobalEnvironment,
                 function.Strict);
+            
+            functionObject.MakeConstructor();
 
             // the function is not actually a named function
             functionObject.SetFunctionName(_functionNameAnonymous, force: true);
@@ -132,47 +134,10 @@ namespace Jint.Native.Function
                 functionDeclaration,
                 env, 
                 functionDeclaration.Strict || _engine._isStrict);
+            
+            functionObject.MakeConstructor();
 
             return functionObject;
         }
-
-        public object Apply(JsValue thisObject, JsValue[] arguments)
-        {
-            if (arguments.Length != 2)
-            {
-                ExceptionHelper.ThrowArgumentException("Apply has to be called with two arguments.");
-            }
-
-            var func = thisObject.TryCast<ICallable>();
-            var thisArg = arguments[0];
-            var argArray = arguments[1];
-
-            if (func is null)
-            {
-                return ExceptionHelper.ThrowTypeError<object>(Engine);
-            }
-
-            if (argArray.IsNullOrUndefined())
-            {
-                return func.Call(thisArg, Arguments.Empty);
-            }
-
-            var argArrayObj = argArray.TryCast<ObjectInstance>();
-            if (ReferenceEquals(argArrayObj, null))
-            {
-                ExceptionHelper.ThrowTypeError(Engine);
-            }
-
-            var len = argArrayObj.Get(CommonProperties.Length, argArrayObj);
-            var n = TypeConverter.ToUint32(len);
-            var argList = new JsValue[n];
-            for (var index = 0; index < n; index++)
-            {
-                var indexName = TypeConverter.ToString(index);
-                var nextArg = argArrayObj.Get(JsString.Create(indexName), argArrayObj);
-                argList[index] = nextArg;
-            }
-            return func.Call(thisArg, argList);
-        }
     }
 }

+ 105 - 16
Jint/Native/Function/FunctionInstance.cs

@@ -1,5 +1,7 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Runtime.CompilerServices;
+using Esprima.Ast;
 using Jint.Native.Object;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
@@ -10,21 +12,16 @@ namespace Jint.Native.Function
 {
     public abstract class FunctionInstance : ObjectInstance, ICallable
     {
-        internal enum FunctionThisMode
-        {
-            Lexical,
-            Strict,
-            Global
-        }
-
-        protected internal PropertyDescriptor _prototypeDescriptor;
+        protected PropertyDescriptor _prototypeDescriptor;
 
         protected internal PropertyDescriptor _length;
-        internal PropertyDescriptor _nameDescriptor;
+        private PropertyDescriptor _nameDescriptor;
 
         protected internal LexicalEnvironment _environment;
         internal readonly JintFunctionDefinition _functionDefinition;
         internal readonly FunctionThisMode _thisMode;
+        internal JsValue _homeObject = Undefined;
+        internal ConstructorKind _constructorKind = ConstructorKind.Base;
 
         internal FunctionInstance(
             Engine engine,
@@ -44,7 +41,7 @@ namespace Jint.Native.Function
             ObjectClass objectClass = ObjectClass.Function)
             : base(engine, objectClass)
         {
-            if (!(name is null))
+            if (name is not null)
             {
                 _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable);
             }
@@ -57,6 +54,9 @@ namespace Jint.Native.Function
             : this(engine, name, FunctionThisMode.Global, ObjectClass.Function)
         {
         }
+        
+        // for example RavenDB wants to inspect this
+        public IFunction FunctionDeclaration => _functionDefinition.Function;
 
         /// <summary>
         /// Executed when a function object is used as a function
@@ -68,6 +68,8 @@ namespace Jint.Native.Function
 
         public bool Strict => _thisMode == FunctionThisMode.Strict;
 
+        internal override bool IsConstructor => this is IConstructor;
+
         public virtual bool HasInstance(JsValue v)
         {
             if (!(v is ObjectInstance o))
@@ -234,15 +236,23 @@ namespace Jint.Native.Function
             _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor
+        /// </summary>
+        /// <remarks>
+        /// Uses separate builder to get correct type with state support to prevent allocations.
+        /// </remarks>
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        internal ObjectInstance OrdinaryCreateFromConstructor(JsValue constructor, ObjectInstance intrinsicDefaultProto)
+        internal T OrdinaryCreateFromConstructor<T>(
+            JsValue constructor,
+            ObjectInstance intrinsicDefaultProto,
+            Func<Engine, JsValue, T> objectCreator,
+            JsValue state = null) where T : ObjectInstance
         {
             var proto = GetPrototypeFromConstructor(constructor, intrinsicDefaultProto);
 
-            var obj = new ObjectInstance(_engine)
-            {
-                _prototype = proto
-            };
+            var obj = objectCreator(_engine, state);
+            obj._prototype = proto;
             return obj;
         }
 
@@ -255,6 +265,85 @@ namespace Jint.Native.Function
             //    Set proto to realm's intrinsic object named intrinsicDefaultProto.
             return proto ?? intrinsicDefaultProto;
         }
+        
+        internal void MakeMethod(ObjectInstance homeObject)
+        {
+            _homeObject = homeObject;
+        }
+        
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-ordinarycallbindthis
+        /// </summary>
+        protected void OrdinaryCallBindThis(ExecutionContext calleeContext, JsValue thisArgument)
+        {
+            var thisMode = _thisMode;
+            if (thisMode == FunctionThisMode.Lexical)
+            {
+                return;
+            }
+
+            // Let calleeRealm be F.[[Realm]].
+
+            var localEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment._record;
+            
+            JsValue thisValue;
+            if (_thisMode == FunctionThisMode.Strict)
+            {
+                thisValue = thisArgument;
+            }
+            else
+            {
+                if (thisArgument.IsNullOrUndefined())
+                {
+                    // Let globalEnv be calleeRealm.[[GlobalEnv]].
+                    var globalEnv = _engine.GlobalEnvironment;
+                    var globalEnvRec = (GlobalEnvironmentRecord) globalEnv._record;
+                    thisValue = globalEnvRec.GlobalThisValue;
+                }
+                else
+                {
+                    thisValue = TypeConverter.ToObject(_engine, thisArgument);
+                }
+            }
+
+            localEnv.BindThisValue(thisValue);
+        }
+
+        protected Completion OrdinaryCallEvaluateBody(
+            JsValue[] arguments,
+            ExecutionContext calleeContext)
+        {
+            var argumentsInstance = _engine.FunctionDeclarationInstantiation(
+                functionInstance: this,
+                arguments,
+                calleeContext.LexicalEnvironment);
+
+            var result = _functionDefinition.Execute();
+            var value = result.GetValueOrDefault().Clone();
+
+            argumentsInstance?.FunctionWasCalled();
+
+            return new Completion(result.Type, value, result.Identifier, result.Location);
+        }
+
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-prepareforordinarycall
+        /// </summary>
+        protected ExecutionContext PrepareForOrdinaryCall(JsValue newTarget)
+        {
+            // ** PrepareForOrdinaryCall **
+            // var callerContext = _engine.ExecutionContext;
+            // Let calleeRealm be F.[[Realm]].
+            // Set the Realm of calleeContext to calleeRealm.
+            // Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
+            var calleeContext = LexicalEnvironment.NewFunctionEnvironment(_engine, this, newTarget);
+            // If callerContext is not already suspended, suspend callerContext.
+            // Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
+            // NOTE: Any exception objects produced after this point are associated with calleeRealm.
+            // Return calleeContext.
+
+            return _engine.EnterExecutionContext(calleeContext, calleeContext);
+        }
 
         public override string ToString()
         {

+ 18 - 8
Jint/Native/Function/FunctionPrototype.cs

@@ -65,19 +65,13 @@ namespace Jint.Native.Function
 
         private JsValue Bind(JsValue thisObj, JsValue[] arguments)
         {
-            if (!(thisObj is ICallable))
+            if (thisObj is not ICallable)
             {
                 ExceptionHelper.ThrowTypeError(Engine, "Bind must be called on a function");
             }
 
             var thisArg = arguments.At(0);
-            var f = new BindFunctionInstance(Engine)
-            {
-                TargetFunction = thisObj,
-                BoundThis = thisObj is ArrowFunctionInstance ? Undefined : thisArg,
-                BoundArgs = arguments.Skip(1),
-                _prototype = Engine.Function.PrototypeObject,
-            };
+            var f = BoundFunctionCreate((ObjectInstance) thisObj, thisArg, arguments.Skip(1));
 
             JsNumber l;
             var targetHasLength = thisObj.HasOwnProperty(CommonProperties.Length);
@@ -114,6 +108,22 @@ namespace Jint.Native.Function
             return f;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-boundfunctioncreate
+        /// </summary>
+        private FunctionInstance BoundFunctionCreate(ObjectInstance targetFunction, JsValue boundThis, JsValue[] boundArgs)
+        {
+            var proto = targetFunction.GetPrototypeOf();
+            var obj = new BindFunctionInstance(Engine)
+            {
+                _prototype = proto,
+                TargetFunction = targetFunction,
+                BoundThis = boundThis,
+                BoundArgs = boundArgs
+            };
+            return obj;
+        }
+
         private JsValue ToString(JsValue thisObj, JsValue[] arguments)
         {
             if (thisObj.IsObject() && thisObj.IsCallable)

+ 9 - 0
Jint/Native/Function/FunctionThisMode.cs

@@ -0,0 +1,9 @@
+namespace Jint.Native.Function
+{
+    internal enum FunctionThisMode
+    {
+        Lexical,
+        Strict,
+        Global
+    }
+}

+ 91 - 69
Jint/Native/Function/ScriptFunctionInstance.cs

@@ -11,7 +11,7 @@ namespace Jint.Native.Function
 {
     public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor
     {
-        private readonly JintFunctionDefinition _function;
+        private bool _isClassConstructor;
 
         /// <summary>
         /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2
@@ -20,8 +20,9 @@ namespace Jint.Native.Function
             Engine engine,
             IFunction functionDeclaration,
             LexicalEnvironment scope,
-            bool strict)
-            : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Global)
+            bool strict,
+            ObjectInstance proto = null)
+            : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Global, proto)
         {
         }
 
@@ -29,74 +30,34 @@ namespace Jint.Native.Function
             Engine engine,
             JintFunctionDefinition function,
             LexicalEnvironment scope,
-            FunctionThisMode thisMode)
+            FunctionThisMode thisMode,
+            ObjectInstance proto = null)
             : base(engine, function, scope, thisMode)
         {
-            _function = function;
-
-            _prototype = _engine.Function.PrototypeObject;
-
+            _prototype = proto ?? _engine.Function.PrototypeObject;
             _length = new LazyPropertyDescriptor(() => JsNumber.Create(function.Initialize(engine, this).Length), PropertyFlag.Configurable);
 
-            var proto = new ObjectInstanceWithConstructor(engine, this)
-            {
-                _prototype = _engine.Object.PrototypeObject
-            };
-
-            _prototypeDescriptor = new PropertyDescriptor(proto, PropertyFlag.OnlyWritable);
-
-            if (!function.Strict && !engine._isStrict)
+            if (!function.Strict && !engine._isStrict && function.Function is not ArrowFunctionExpression)
             {
                 DefineOwnProperty(CommonProperties.Arguments, engine._callerCalleeArgumentsThrowerConfigurable);
                 DefineOwnProperty(CommonProperties.Caller, new PropertyDescriptor(Undefined, PropertyFlag.Configurable));
             }
         }
 
-        // for example RavenDB wants to inspect this
-        public IFunction FunctionDeclaration => _function.Function;
-
         /// <summary>
         /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
         /// </summary>
         public override JsValue Call(JsValue thisArgument, JsValue[] arguments)
         {
-            // ** PrepareForOrdinaryCall **
-            // var callerContext = _engine.ExecutionContext;
-            // Let calleeRealm be F.[[Realm]].
-            // Set the Realm of calleeContext to calleeRealm.
-            // Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
-            var localEnv = LexicalEnvironment.NewFunctionEnvironment(_engine, this, Undefined);
-            // If callerContext is not already suspended, suspend callerContext.
-            // Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
-            // NOTE: Any exception objects produced after this point are associated with calleeRealm.
-            // Return calleeContext.
-
-            _engine.EnterExecutionContext(localEnv, localEnv);
-
-            // ** OrdinaryCallBindThis **
-            
-            JsValue thisValue;
-            if (_thisMode == FunctionThisMode.Strict)
+            if (_isClassConstructor)
             {
-                thisValue = thisArgument;
-            }
-            else
-            {
-                if (thisArgument.IsNullOrUndefined())
-                {
-                    var globalEnv = _engine.GlobalEnvironment;
-                    var globalEnvRec = (GlobalEnvironmentRecord) globalEnv._record;
-                    thisValue = globalEnvRec.GlobalThisValue;
-                }
-                else
-                {
-                    thisValue = TypeConverter.ToObject(_engine, thisArgument);
-                }
+                ExceptionHelper.ThrowTypeError(_engine, $"Class constructor {_functionDefinition.Name} cannot be invoked without 'new'");
             }
 
-            var envRec = (FunctionEnvironmentRecord) localEnv._record;
-            envRec.BindThisValue(thisValue);
-            
+            var calleeContext = PrepareForOrdinaryCall(Undefined);
+
+            OrdinaryCallBindThis(calleeContext, thisArgument);
+
             // actual call
 
             var strict = _thisMode == FunctionThisMode.Strict || _engine._isStrict;
@@ -104,23 +65,16 @@ namespace Jint.Native.Function
             {
                 try
                 {
-                    var argumentsInstance = _engine.FunctionDeclarationInstantiation(
-                        functionInstance: this,
-                        arguments,
-                        localEnv);
-
-                    var result = _function.Execute();
-                    var value = result.GetValueOrDefault().Clone();
-                    argumentsInstance?.FunctionWasCalled();
+                    var result = OrdinaryCallEvaluateBody(arguments, calleeContext);
 
                     if (result.Type == CompletionType.Throw)
                     {
-                        ExceptionHelper.ThrowJavaScriptException(_engine, value, result);
+                        ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result);
                     }
 
                     if (result.Type == CompletionType.Return)
                     {
-                        return value;
+                        return result.Value;
                     }
                 }
                 finally
@@ -132,22 +86,90 @@ namespace Jint.Native.Function
             }
         }
 
+        internal override bool IsConstructor =>
+            (_homeObject.IsUndefined() || _isClassConstructor) 
+            && _functionDefinition?.Function is not ArrowFunctionExpression;
+
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2
+        /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
         /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            var thisArgument = OrdinaryCreateFromConstructor(TypeConverter.ToObject(_engine, newTarget), _engine.Object.PrototypeObject);
+            var kind = _constructorKind;
 
-            var result = Call(thisArgument, arguments).TryCast<ObjectInstance>();
-            if (!ReferenceEquals(result, null))
+            var thisArgument = Undefined;
+            
+            if (kind == ConstructorKind.Base)
+            {
+                thisArgument = OrdinaryCreateFromConstructor(newTarget, _engine.Object.PrototypeObject, static (engine, _) => new ObjectInstance(engine));
+            }
+
+            var calleeContext = PrepareForOrdinaryCall(newTarget);
+
+            if (kind == ConstructorKind.Base)
             {
-                return result;
+                OrdinaryCallBindThis(calleeContext, thisArgument);
             }
 
-            return thisArgument;
+            var constructorEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment._record;
+            
+            var strict = _thisMode == FunctionThisMode.Strict || _engine._isStrict;
+            using (new StrictModeScope(strict, force: true))
+            {
+                try
+                {
+                    var result = OrdinaryCallEvaluateBody(arguments, calleeContext);
+
+                    if (result.Type == CompletionType.Return)
+                    {
+                        if (result.Value is ObjectInstance oi)
+                        {
+                            return oi;
+                        }
+
+                        if (kind == ConstructorKind.Base)
+                        {
+                            return (ObjectInstance) thisArgument!;
+                        }
+
+                        if (!result.Value.IsUndefined())
+                        {
+                            ExceptionHelper.ThrowTypeError(_engine);
+                        }
+                    }
+                    else if (result.Type == CompletionType.Throw)
+                    {
+                        ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result);
+                    }
+                }
+                finally
+                {
+                    _engine.LeaveExecutionContext();
+                }
+            }
+
+            return (ObjectInstance) constructorEnv.GetThisBinding();
         }
+        
+        internal void MakeConstructor(bool writableProperty = true, ObjectInstance prototype = null)
+        {
+            _constructorKind = ConstructorKind.Base;
+            if (prototype is null)
+            {
+                prototype = new ObjectInstanceWithConstructor(_engine, this)
+                {
+                    _prototype = _engine.Object.PrototypeObject
+                };
+            }
 
+            _prototypeDescriptor = new PropertyDescriptor(prototype, writableProperty, enumerable: false, configurable: false);
+        }        
+
+        internal void MakeClassConstructor()
+        {
+            _isClassConstructor = true;
+        }
+        
         private class ObjectInstanceWithConstructor : ObjectInstance
         {
             private PropertyDescriptor _constructor;

+ 4 - 4
Jint/Native/Global/GlobalObject.cs

@@ -66,8 +66,8 @@ namespace Jint.Native.Global
                 ["decodeURIComponent"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "decodeURIComponent", DecodeUriComponent, 1, lengthFlags), propertyFlags),
                 ["encodeURI"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "encodeURI", EncodeUri, 1, lengthFlags), propertyFlags),
                 ["encodeURIComponent"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "encodeURIComponent", EncodeUriComponent, 1, lengthFlags), propertyFlags),
-                ["escape"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "escape", Escape, 1), propertyFlags),
-                ["unescape"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "unescape", Unescape, 1), propertyFlags),
+                ["escape"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "escape", Escape, 1, lengthFlags), propertyFlags),
+                ["unescape"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "unescape", Unescape, 1, lengthFlags), propertyFlags),
                 ["globalThis"] = new PropertyDescriptor(this, propertyFlags),
 
                 // toString is not mentioned or actually required in spec, but some tests rely on it
@@ -679,7 +679,7 @@ namespace Jint.Native.Global
                 var c = uriString[k];
                 if (c == '%')
                 {
-                    if (k < strLen - 6
+                    if (k <= strLen - 6
                         && uriString[k + 1] == 'u'
                         && uriString.Skip(k + 2).Take(4).All(IsValidHexaChar))
                     {
@@ -689,7 +689,7 @@ namespace Jint.Native.Global
 
                         k += 5;
                     }
-                    else if (k < strLen - 3
+                    else if (k <= strLen - 3
                         && uriString.Skip(k + 1).Take(2).All(IsValidHexaChar))
                     {
                         c = (char)int.Parse(

+ 5 - 2
Jint/Native/Iterator/IteratorInstance.cs

@@ -329,15 +329,18 @@ namespace Jint.Native.Iterator
 
             public void Close(CompletionType completion)
             {
-                if (!_target.TryGetValue(CommonProperties.Return, out var func))
+                if (!_target.TryGetValue(CommonProperties.Return, out var func)
+                    || func.IsNullOrUndefined())
                 {
                     return;
                 }
 
+                var callable = func as ICallable ?? ExceptionHelper.ThrowTypeError<ICallable>(_target.Engine, func + " is not a function");
+
                 var innerResult = Undefined;
                 try
                 {
-                    innerResult = ((ICallable) func).Call(_target, Arguments.Empty);
+                    innerResult = callable.Call(_target, Arguments.Empty);
                 }
                 catch
                 {

+ 1 - 1
Jint/Native/JsNumber.cs

@@ -166,7 +166,7 @@ namespace Jint.Native
 
         public override string ToString()
         {
-            return EsprimaExtensions.DoubleToString(_value);
+            return TypeConverter.ToString(_value);
         }
 
         public override bool Equals(JsValue obj)

+ 1 - 1
Jint/Native/JsValue.cs

@@ -271,7 +271,7 @@ namespace Jint.Native
                 : (Types) (_type & ~InternalTypes.InternalFlags);
         }
 
-        internal virtual bool IsConstructor => this is IConstructor;
+        internal virtual bool IsConstructor => false;
 
         /// <summary>
         /// Creates a valid <see cref="JsValue"/> instance from any <see cref="Object"/> instance

+ 47 - 60
Jint/Native/Json/JsonInstance.cs

@@ -1,5 +1,6 @@
 using Jint.Collections;
 using Jint.Native.Object;
+using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
 using Jint.Runtime.Interop;
@@ -8,8 +9,6 @@ namespace Jint.Native.Json
 {
     public sealed class JsonInstance : ObjectInstance
     {
-        private JsValue _reviver;
-
         private JsonInstance(Engine engine)
             : base(engine, objectClass: ObjectClass.JSON)
         {
@@ -32,107 +31,95 @@ namespace Jint.Native.Json
                 ["stringify"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "stringify", Stringify, 3), true, false, true)
             };
             SetProperties(properties);
+            
+            var symbols = new SymbolDictionary(1)
+            {
+                [GlobalSymbolRegistry.ToStringTag] = new PropertyDescriptor("JSON", false, false, true),
+            };
+            SetSymbols(symbols);
         }
 
-        private JsValue AbstractWalkOperation(ObjectInstance thisObject, JsValue prop)
+        private static JsValue InternalizeJSONProperty(JsValue holder, JsValue name, ICallable reviver)
         {
-            JsValue value = thisObject.Get(prop, thisObject);
-            if (value.IsObject())
+            JsValue temp = holder.Get(name, holder);
+            if (temp is ObjectInstance val)
             {
-                var valueAsObject = value.AsObject();
-                if (valueAsObject.Class == ObjectClass.Array)
+                if (val.IsArray())
                 {
-                    var valAsArray = value.AsArray();
-                    var i = 0;
-                    var arrLen = valAsArray.GetLength();
-                    while (i < arrLen)
+                    var i = 0UL;
+                    var len = TypeConverter.ToLength(val.Get(CommonProperties.Length));
+                    while (i < len)
                     {
-                        var newValue = AbstractWalkOperation(valAsArray, JsString.Create(i));
-                        if (newValue.IsUndefined())
+                        var prop = JsString.Create(i);
+                        var newElement = InternalizeJSONProperty(val, prop, reviver);
+                        if (newElement.IsUndefined())
                         {
-                            valAsArray.Delete(JsString.Create(i));
+                            val.Delete(prop);
                         }
                         else
                         {
-                            valAsArray.DefineOwnProperty
-                            (
-                                JsString.Create(i),
-                                new PropertyDescriptor
-                                (
-                                    value: newValue,
-                                    PropertyFlag.ConfigurableEnumerableWritable
-                                ));
+                            val.CreateDataProperty(prop, newElement);
                         }
                         i = i + 1;
                     }
                 }
                 else
                 {
-                    var keys = valueAsObject.GetOwnProperties();
+                    var keys = val.EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind.Key);
                     foreach (var p in keys)
                     {
-                        var newElement = AbstractWalkOperation(valueAsObject, p.Key);
+                        var newElement = InternalizeJSONProperty(val, p, reviver);
                         if (newElement.IsUndefined())
                         {
-                            valueAsObject.Delete(p.Key);
+                            val.Delete(p);
                         }
                         else
                         {
-                            valueAsObject.DefineOwnProperty(
-                                p.Key,
-                                new PropertyDescriptor
-                                (
-                                    value: newElement,
-                                    PropertyFlag.ConfigurableEnumerableWritable
-                                ));
+                            val.CreateDataProperty(p, newElement);
                         }
                     }
                 }
             }
-            return _reviver.Invoke(thisObject, new[] { prop, value });
+
+            return reviver.Call(holder, new[] { name, temp });
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-json.parse
+        /// </summary>
         public JsValue Parse(JsValue thisObject, JsValue[] arguments)
         {
+            var jsonString = TypeConverter.ToString(arguments.At(0));
+            var reviver = arguments.At(1);
+
             var parser = new JsonParser(_engine);
-            var res = parser.Parse(TypeConverter.ToString(arguments[0]));
-            if (arguments.Length > 1)
+            var unfiltered = parser.Parse(jsonString);
+
+            if (reviver.IsCallable)
             {
-                _reviver = arguments[1];
-                ObjectInstance revRes = _engine.Object.Construct(Arguments.Empty);
-                revRes.SetProperty("", new PropertyDescriptor(value: res, PropertyFlag.ConfigurableEnumerableWritable));
-                return AbstractWalkOperation(revRes, JsString.Empty);
+                var root = _engine.Object.Construct(Arguments.Empty);
+                var rootName = JsString.Empty;
+                root.CreateDataPropertyOrThrow(rootName, unfiltered);
+                return InternalizeJSONProperty(root, rootName, (ICallable) reviver);
+            }
+            else
+            {
+                return unfiltered;
             }
-            return res;
         }
 
         public JsValue Stringify(JsValue thisObject, JsValue[] arguments)
         {
-            JsValue
-                value = Undefined,
-                replacer = Undefined,
-                space = Undefined;
+            var value = arguments.At(0);
+            var replacer = arguments.At(1);
+            var space = arguments.At(2);
 
-            if (arguments.Length > 2)
+            if (value.IsUndefined() && replacer.IsUndefined()) 
             {
-                space = arguments[2];
-            }
-
-            if (arguments.Length > 1)
-            {
-                replacer = arguments[1];
-            }
-
-            if (arguments.Length > 0)
-            {
-                value = arguments[0];
-            }
-
-            var serializer = new JsonSerializer(_engine);
-            if (value.IsUndefined() && replacer.IsUndefined()) {
                 return Undefined;
             }
 
+            var serializer = new JsonSerializer(_engine);
             return serializer.Serialize(value, replacer, space);
         }
     }

+ 1 - 1
Jint/Native/Json/JsonParser.cs

@@ -797,7 +797,7 @@ namespace Jint.Native.Json
         {
             _source = code;
             _index = 0;
-            _lineNumber = (_source.Length > 0) ? 1 : 0;
+            _lineNumber = 1;
             _lineStart = 0;
             _length = _source.Length;
             _lookahead = null;

+ 7 - 13
Jint/Native/Json/JsonSerializer.cs

@@ -1,6 +1,5 @@
 using System.Collections.Generic;
 using System.Linq;
-using Jint.Native.Array;
 using Jint.Native.Global;
 using Jint.Native.Object;
 using Jint.Runtime;
@@ -28,7 +27,7 @@ namespace Jint.Native.Json
 
             // for JSON.stringify(), any function passed as the first argument will return undefined
             // if the replacer is not defined. The function is not called either.
-            if (value is ICallable callable && ReferenceEquals(replacer, Undefined.Instance))
+            if (value.IsCallable && ReferenceEquals(replacer, Undefined.Instance))
             {
                 return Undefined.Instance;
             }
@@ -119,9 +118,8 @@ namespace Jint.Native.Json
             return Str(JsString.Empty, wrapper);
         }
 
-        private JsValue Str(JsValue key, ObjectInstance holder)
+        private JsValue Str(JsValue key, JsValue holder)
         {
-
             var value = holder.Get(key, holder);
             if (value.IsObject())
             {
@@ -141,7 +139,6 @@ namespace Jint.Native.Json
                 value = replacerFunctionCallable.Call(holder, Arguments.From(key, value));
             }
 
-
             if (value.IsObject())
             {
                 var valueObj = value.AsObject();
@@ -157,7 +154,7 @@ namespace Jint.Native.Json
                         value = TypeConverter.ToPrimitive(value);
                         break;
                     case ObjectClass.Array:
-                        value = SerializeArray(value.As<ArrayInstance>());
+                        value = SerializeArray(value);
                         return value;
                     case ObjectClass.Object:
                         value = SerializeObject(value.AsObject());
@@ -195,12 +192,9 @@ namespace Jint.Native.Json
 
             if (value.IsObject() && isCallable == false)
             {
-                if (value.AsObject().Class == ObjectClass.Array)
-                {
-                    return SerializeArray(value.As<ArrayInstance>());
-                }
-
-                return SerializeObject(value.AsObject());
+                return value.AsObject().Class == ObjectClass.Array 
+                    ? SerializeArray(value)
+                    : SerializeObject(value.AsObject());
             }
 
             return JsValue.Undefined;
@@ -251,7 +245,7 @@ namespace Jint.Native.Json
             return sb.ToString();
         }
 
-        private string SerializeArray(ArrayInstance value)
+        private string SerializeArray(JsValue value)
         {
             EnsureNonCyclicity(value);
             _stack.Push(value);

+ 7 - 4
Jint/Native/Map/MapConstructor.cs

@@ -5,7 +5,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Map
@@ -63,13 +62,17 @@ namespace Jint.Native.Map
             return Construct(arguments, thisObject);
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-map-iterable
+        /// </summary>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            var map = new MapInstance(Engine)
+            if (newTarget.IsUndefined())
             {
-                _prototype = PrototypeObject
-            };
+                ExceptionHelper.ThrowTypeError(_engine);
+            }
 
+            var map = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, _) => new MapInstance(engine));
             if (arguments.Length > 0 && !arguments[0].IsNullOrUndefined())
             {
                 var adder = map.Get("set");

+ 0 - 1
Jint/Native/Map/MapPrototype.cs

@@ -3,7 +3,6 @@ using Jint.Native.Object;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 
 namespace Jint.Native.Map

+ 12 - 9
Jint/Native/Number/NumberConstructor.cs

@@ -131,22 +131,25 @@ namespace Jint.Native.Number
         }
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.2.1
+        /// https://tc39.es/ecma262/#sec-number-constructor-number-value
         /// </summary>
-        /// <param name="arguments"></param>
-        /// <returns></returns>
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
-            return Construct(arguments.Length > 0 ? TypeConverter.ToNumber(arguments[0]) : 0);
-        }
+            var value = arguments.Length > 0 
+                ? JsNumber.Create(TypeConverter.ToNumber(arguments[0]))
+                : JsNumber.PositiveZero;
 
-        public NumberPrototype PrototypeObject { get; private set; }
+            if (newTarget.IsUndefined())
+            {
+                return Construct(value);
+            }
 
-        public NumberInstance Construct(double value)
-        {
-            return Construct(JsNumber.Create(value));
+            var o = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, state) => new NumberInstance(engine, (JsNumber) state), value);
+            return o;
         }
 
+        public NumberPrototype PrototypeObject { get; private set; }
+
         public NumberInstance Construct(JsNumber value)
         {
             var instance = new NumberInstance(Engine)

+ 6 - 0
Jint/Native/Number/NumberInstance.cs

@@ -14,6 +14,12 @@ namespace Jint.Native.Number
         {
         }
 
+        public NumberInstance(Engine engine, JsNumber value)
+            : base(engine, ObjectClass.Number)
+        {
+            NumberData = value;
+        }
+
         Types IPrimitiveInstance.Type => Types.Number;
 
         JsValue IPrimitiveInstance.PrimitiveValue => NumberData;

+ 6 - 1
Jint/Native/Object/ObjectConstructor.cs

@@ -41,7 +41,7 @@ namespace Jint.Native.Object
                 ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Entries, 1, lengthFlags), propertyFlags),
                 ["fromEntries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromEntries", FromEntries, 1, lengthFlags), propertyFlags),
                 ["getPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getPrototypeOf", GetPrototypeOf, 1), propertyFlags),
-                ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2), propertyFlags),
+                ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2, lengthFlags), propertyFlags),
                 ["getOwnPropertyDescriptors"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptors", GetOwnPropertyDescriptors, 1, lengthFlags), propertyFlags),
                 ["getOwnPropertyNames"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyNames", GetOwnPropertyNames, 1), propertyFlags),
                 ["getOwnPropertySymbols"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertySymbols", GetOwnPropertySymbols, 1, lengthFlags), propertyFlags),
@@ -153,6 +153,11 @@ namespace Jint.Native.Object
 
         public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget)
         {
+            if (!ReferenceEquals(this, newTarget) && !newTarget.IsUndefined())
+            {
+                return OrdinaryCreateFromConstructor(newTarget, _engine.Object.PrototypeObject, (engine, state) => new ObjectInstance(engine));
+            }
+            
             if (arguments.Length > 0)
             {
                 var value = arguments[0];

+ 19 - 12
Jint/Native/Object/ObjectInstance.cs

@@ -14,7 +14,6 @@ using Jint.Native.String;
 using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Descriptors;
-using Jint.Runtime.Descriptors.Specialized;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interpreter.Expressions;
 
@@ -257,7 +256,6 @@ namespace Jint.Native.Object
             return keys;
         }
 
-
         protected virtual void AddProperty(JsValue property, PropertyDescriptor descriptor)
         {
             SetProperty(property, descriptor);
@@ -343,9 +341,8 @@ namespace Jint.Native.Object
                 return Undefined;
             }
 
-            // if getter is not undefined it must be ICallable
-            var callable = getter.TryCast<ICallable>();
-            return callable.Call(thisObject, Arguments.Empty);
+            var functionInstance = (FunctionInstance) getter;
+            return functionInstance._engine.Call(functionInstance, thisObject, Arguments.Empty, expression: null);
         }
 
         /// <summary>
@@ -503,7 +500,8 @@ namespace Jint.Native.Object
                 return false;
             }
 
-            setter.Call(receiver, new[] {value});
+            var functionInstance = (FunctionInstance) setter;
+            _engine.Call(functionInstance, receiver, new[] { value }, expression: null);
 
             return true;
         }
@@ -624,7 +622,7 @@ namespace Jint.Native.Object
         {
             if (!DefineOwnProperty(property, desc))
             {
-                ExceptionHelper.ThrowTypeError(_engine);
+                ExceptionHelper.ThrowTypeError(_engine, "Cannot redefine property: " + property);
             }
 
             return true;
@@ -861,7 +859,7 @@ namespace Jint.Native.Object
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        protected void EnsureInitialized()
+        protected internal void EnsureInitialized()
         {
             if (_initialized)
             {
@@ -1105,7 +1103,7 @@ namespace Jint.Native.Object
             return JsBoolean.True;
         }
 
-        protected virtual ObjectInstance GetPrototypeOf()
+        protected internal virtual ObjectInstance GetPrototypeOf()
         {
             return _prototype;
         }
@@ -1160,6 +1158,15 @@ namespace Jint.Native.Object
             return true;
         }
 
+        /// <summary>
+        /// https://tc39.es/ecma262/#sec-createmethodproperty
+        /// </summary>
+        internal virtual bool CreateMethodProperty(JsValue p, JsValue v)
+        {
+            var newDesc = new PropertyDescriptor(v, PropertyFlag.NonEnumerable);
+            return DefineOwnProperty(p, newDesc);
+        }
+        
         /// <summary>
         /// https://tc39.es/ecma262/#sec-createdatapropertyorthrow
         /// </summary>
@@ -1201,13 +1208,13 @@ namespace Jint.Native.Object
 
         internal void CopyDataProperties(
             ObjectInstance target,
-            HashSet<JsValue> processedProperties)
+            HashSet<JsValue> excludedItems)
         {
             var keys = GetOwnPropertyKeys();
             for (var i = 0; i < keys.Count; i++)
             {
                 var key = keys[i];
-                if (processedProperties == null || !processedProperties.Contains(key))
+                if (excludedItems == null || !excludedItems.Contains(key))
                 {
                     var desc = GetOwnProperty(key);
                     if (desc.Enumerable)
@@ -1218,7 +1225,7 @@ namespace Jint.Native.Object
             }
         }
 
-        internal JsValue EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind kind)
+        internal ArrayInstance EnumerableOwnPropertyNames(EnumerableOwnPropertyNamesKind kind)
         {
             var ownKeys = GetOwnPropertyKeys(Types.String);
 

+ 1 - 4
Jint/Native/Object/ObjectPrototype.cs

@@ -96,11 +96,8 @@ namespace Jint.Native.Object
         }
 
         /// <summary>
-        /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2
+        /// https://tc39.es/ecma262/#sec-object.prototype.tostring
         /// </summary>
-        /// <param name="thisObject"></param>
-        /// <param name="arguments"></param>
-        /// <returns></returns>
         public JsValue ToObjectString(JsValue thisObject, JsValue[] arguments)
         {
             if (thisObject.IsUndefined())

Some files were not shown because too many files changed in this diff