瀏覽代碼

Module support fixes and improvements (#1102)

Co-authored-by: Marko Lahma <[email protected]>
Christian Rondeau 3 年之前
父節點
當前提交
6098a14c54
共有 34 個文件被更改,包括 1638 次插入1260 次删除
  1. 0 51
      Jint.Tests.Test262/Language/ModuleTestHost.cs
  2. 3 46
      Jint.Tests.Test262/Language/ModuleTests.cs
  3. 2 2
      Jint.Tests.Test262/SingleTest.cs
  4. 26 9
      Jint.Tests.Test262/Test262Test.cs
  5. 8 0
      Jint.Tests.Test262/test/skipped.json
  6. 100 10
      Jint.Tests/Runtime/ModuleTests.cs
  7. 74 47
      Jint/Engine.Modules.cs
  8. 6 7
      Jint/Engine.cs
  9. 25 13
      Jint/EsprimaExtensions.cs
  10. 16 9
      Jint/HoistingScope.cs
  11. 57 7
      Jint/ModuleBuilder.cs
  12. 1 1
      Jint/Native/Array/ArrayPrototype.cs
  13. 2 2
      Jint/Native/Function/ClassDefinition.cs
  14. 1 2
      Jint/Options.cs
  15. 15 3
      Jint/Runtime/Environments/ModuleEnvironmentRecord.cs
  16. 25 22
      Jint/Runtime/Host.cs
  17. 2 2
      Jint/Runtime/IScriptOrModule.Extensions.cs
  18. 1 0
      Jint/Runtime/IScriptOrModule.cs
  19. 2 1
      Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs
  20. 1 1
      Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs
  21. 1 0
      Jint/Runtime/Interpreter/Expressions/JintExpression.cs
  22. 39 0
      Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs
  23. 14 4
      Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs
  24. 1 1
      Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs
  25. 73 8
      Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs
  26. 0 42
      Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs
  27. 3 9
      Jint/Runtime/Interpreter/Statements/JintImportDeclaration.cs
  28. 46 0
      Jint/Runtime/Modules/BuilderModuleRecord.cs
  29. 588 0
      Jint/Runtime/Modules/CyclicModuleRecord.cs
  30. 5 0
      Jint/Runtime/Modules/DefaultModuleLoader.cs
  31. 0 952
      Jint/Runtime/Modules/JsModule.cs
  32. 74 9
      Jint/Runtime/Modules/ModuleNamespace.cs
  33. 86 0
      Jint/Runtime/Modules/ModuleRecord.cs
  34. 341 0
      Jint/Runtime/Modules/SourceTextModuleRecord.cs

+ 0 - 51
Jint.Tests.Test262/Language/ModuleTestHost.cs

@@ -1,51 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Reflection;
-using System.IO;
-using Jint.Native;
-using Jint.Native.Object;
-using Jint.Runtime;
-using Jint.Runtime.Interop;
-
-namespace Jint.Tests.Test262.Language
-{
-    // Hacky way to get objects from assert.js and sta.js into the module context
-    internal sealed class ModuleTestHost : Host
-    {
-        private readonly static Dictionary<string, JsValue> _staticValues = new();
-
-        static ModuleTestHost()
-        {
-            var assemblyPath = new Uri(typeof(ModuleTestHost).GetTypeInfo().Assembly.Location).LocalPath;
-            var assemblyDirectory = new FileInfo(assemblyPath).Directory;
-
-            var basePath = assemblyDirectory.Parent.Parent.Parent.FullName;
-
-            var engine = new Engine();
-            var assertSource = File.ReadAllText(Path.Combine(basePath, "harness", "assert.js"));
-            var staSource = File.ReadAllText(Path.Combine(basePath, "harness", "sta.js"));
-
-            engine.Execute(assertSource);
-            engine.Execute(staSource);
-
-            _staticValues["assert"] = engine.GetValue("assert");
-            _staticValues["Test262Error"] = engine.GetValue("Test262Error");
-            _staticValues["$ERROR"] = engine.GetValue("$ERROR");
-            _staticValues["$DONOTEVALUATE"] = engine.GetValue("$DONOTEVALUATE");
-
-            _staticValues["print"] = new ClrFunctionInstance(engine, "print", (thisObj, args) => TypeConverter.ToString(args.At(0)));
-        }
-
-        protected override ObjectInstance CreateGlobalObject(Realm realm)
-        {
-            var globalObj = base.CreateGlobalObject(realm);
-
-            foreach (var key in _staticValues.Keys)
-            {
-                globalObj.FastAddProperty(key, _staticValues[key], true, true, true);
-            }
-
-            return globalObj;
-        }
-    }
-}

+ 3 - 46
Jint.Tests.Test262/Language/ModuleTests.cs

@@ -1,10 +1,4 @@
-using Jint.Runtime;
-using Jint.Runtime.Modules;
-using System;
-using System.IO;
-using System.Reflection;
 using Xunit;
-using Xunit.Sdk;
 
 namespace Jint.Tests.Test262.Language;
 
@@ -15,7 +9,7 @@ public class ModuleTests : Test262Test
     [MemberData(nameof(SourceFiles), "language\\module-code", true, Skip = "Skipped")]
     protected void ModuleCode(SourceFile sourceFile)
     {
-        RunModuleTest(sourceFile);
+        RunTestInternal(sourceFile);
     }
 
     [Theory(DisplayName = "language\\export")]
@@ -23,7 +17,7 @@ public class ModuleTests : Test262Test
     [MemberData(nameof(SourceFiles), "language\\export", true, Skip = "Skipped")]
     protected void Export(SourceFile sourceFile)
     {
-        RunModuleTest(sourceFile);
+        RunTestInternal(sourceFile);
     }
 
     [Theory(DisplayName = "language\\import")]
@@ -31,43 +25,6 @@ public class ModuleTests : Test262Test
     [MemberData(nameof(SourceFiles), "language\\import", true, Skip = "Skipped")]
     protected void Import(SourceFile sourceFile)
     {
-        RunModuleTest(sourceFile);
-    }
-
-    private static void RunModuleTest(SourceFile sourceFile)
-    {
-        if (sourceFile.Skip)
-        {
-            return;
-        }
-
-        var code = sourceFile.Code;
-
-        var options = new Options();
-        options.Host.Factory = _ => new ModuleTestHost();
-        options.EnableModules(Path.Combine(BasePath, "test"));
-
-        var engine = new Engine(options);
-
-        var negative = code.IndexOf("negative:", StringComparison.OrdinalIgnoreCase) != -1;
-        string lastError = null;
-
-        try
-        {
-            engine.LoadModule(sourceFile.FullPath);
-        }
-        catch (JavaScriptException ex)
-        {
-            lastError = ex.ToString();
-        }
-        catch (Exception ex)
-        {
-            lastError = ex.ToString();
-        }
-
-        if (!negative && !string.IsNullOrWhiteSpace(lastError))
-        {
-            throw new XunitException(lastError);
-        }
+        RunTestInternal(sourceFile);
     }
 }

+ 2 - 2
Jint.Tests.Test262/SingleTest.cs

@@ -34,12 +34,12 @@ namespace Jint.Tests.Test262
 
             if (code.IndexOf("onlyStrict", StringComparison.Ordinal) < 0)
             {
-                RunTestCode(sourceFile.Source, code, strict: false);
+                RunTestCode(sourceFile.Source, code, strict: false, null);
             }
 
             if (code.IndexOf("noStrict", StringComparison.Ordinal) < 0)
             {
-                RunTestCode(sourceFile.Source, code, strict: true);
+                RunTestCode(sourceFile.Source, code, strict: true, null);
             }
         }
     }

+ 26 - 9
Jint.Tests.Test262/Test262Test.cs

@@ -30,6 +30,8 @@ namespace Jint.Tests.Test262
 
         private static readonly HashSet<string> _strictSkips = new(StringComparer.OrdinalIgnoreCase);
 
+        private static readonly Regex _moduleFlagRegex = new Regex(@"flags:\s*?\[.*?module.*?]", RegexOptions.Compiled);
+
         static Test262Test()
         {
             //NOTE: The Date tests in test262 assume the local timezone is Pacific Standard Time
@@ -96,12 +98,19 @@ namespace Jint.Tests.Test262
             }
         }
 
-        protected void RunTestCode(string fileName, string code, bool strict)
+        protected void RunTestCode(string fileName, string code, bool strict, string fullPath)
         {
-            var engine = new Engine(cfg => cfg
-                .LocalTimeZone(_pacificTimeZone)
-                .Strict(strict)
-            );
+            var module = _moduleFlagRegex.IsMatch(code);
+
+            var engine = new Engine(cfg =>
+            {
+                cfg.LocalTimeZone(_pacificTimeZone);
+                cfg.Strict(strict);
+                if (module)
+                {
+                    cfg.EnableModules(Path.Combine(BasePath, "test", Path.GetDirectoryName(fullPath)!));
+                }
+            });
 
             engine.Execute(Sources["sta.js"]);
             engine.Execute(Sources["assert.js"]);
@@ -162,7 +171,15 @@ namespace Jint.Tests.Test262
             bool negative = code.IndexOf("negative:", StringComparison.Ordinal) > -1;
             try
             {
-                engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseScript());
+                if (module)
+                {
+                    engine.AddModule(fullPath, builder => builder.AddSource(code));
+                    engine.ImportModule(fullPath);
+                }
+                else
+                {
+                    engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseScript());
+                }
             }
             catch (JavaScriptException j)
             {
@@ -186,15 +203,15 @@ namespace Jint.Tests.Test262
                 return;
             }
 
-            if (sourceFile.Code.IndexOf("onlyStrict", StringComparison.Ordinal) < 0)
+            if (sourceFile.Code.IndexOf("onlyStrict", StringComparison.Ordinal) < 0 && !_moduleFlagRegex.IsMatch(sourceFile.Code))
             {
-                RunTestCode(sourceFile.Source, sourceFile.Code, strict: false);
+                RunTestCode(sourceFile.Source, sourceFile.Code, strict: false, fullPath: sourceFile.FullPath);
             }
 
             if (!_strictSkips.Contains(sourceFile.Source)
                 && sourceFile.Code.IndexOf("noStrict", StringComparison.Ordinal) < 0)
             {
-                RunTestCode(sourceFile.Source, sourceFile.Code, strict: true);
+                RunTestCode(sourceFile.Source, sourceFile.Code, strict: true, fullPath: sourceFile.FullPath);
             }
         }
 

+ 8 - 0
Jint.Tests.Test262/test/skipped.json

@@ -310,6 +310,14 @@
         "source": "language/statements/class/subclass/builtin-objects/GeneratorFunction/regular-subclassing.js",
         "reason": "generators not implemented"
     },
+    {
+        "source": "language/module-code/instn-local-bndng-export-gen.js",
+        "reason": "generators not implemented"
+    },
+    {
+        "source": "language/module-code/instn-local-bndng-gen.js",
+        "reason": "generators not implemented"
+    },
 
     // Eval problems
 

+ 100 - 10
Jint.Tests/Runtime/ModuleTests.cs

@@ -1,8 +1,10 @@
 #if(NET6_0_OR_GREATER)
+using System;
 using System.IO;
 using System.Reflection;
 #endif
-using System;
+using System.Collections.Generic;
+using System.Linq;
 using Jint.Native;
 using Jint.Runtime;
 using Xunit;
@@ -97,7 +99,41 @@ public class ModuleTests
     }
 
     [Fact]
-    public void ShouldPropagateThrowStatementOnCSharpImport()
+    public void ShouldImportDynamically()
+    {
+        var received = false;
+        _engine.AddModule("imported-module", builder => builder.ExportFunction("signal", () => received = true));
+        _engine.AddModule("my-module", @"import('imported-module').then(ns => { ns.signal(); });");
+
+        _engine.ImportModule("my-module");
+        _engine.RunAvailableContinuations();
+
+        Assert.True(received);
+    }
+
+    [Fact]
+    public void ShouldPropagateParseError()
+    {
+        _engine.AddModule("imported", @"export const invalid;");
+        _engine.AddModule("my-module", @"import { invalid } from 'imported';");
+
+        var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
+        Assert.Equal("Error while loading module: error in module 'imported': Line 1: Missing initializer in const declaration", exc.Message);
+    }
+
+    [Fact]
+    public void ShouldPropagateLinkError()
+    {
+        _engine.AddModule("imported", @"export invalid;");
+        _engine.AddModule("my-module", @"import { value } from 'imported';");
+
+        var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
+        Assert.Equal("Error while loading module: error in module 'imported': Line 1: Unexpected identifier", exc.Message);
+        Assert.Equal("my-module", exc.Location.Source);
+    }
+
+    [Fact]
+    public void ShouldPropagateExecuteError()
     {
         _engine.AddModule("my-module", @"throw new Error('imported successfully');");
 
@@ -114,7 +150,6 @@ public class ModuleTests
 
         var exc = Assert.Throws<JavaScriptException>(() => _engine.ImportModule("my-module"));
         Assert.Equal("imported successfully", exc.Message);
-        Assert.Equal("imported-module", exc.Location.Source);
     }
 
     [Fact]
@@ -157,6 +192,33 @@ public class ModuleTests
         Assert.Equal("hello world", ns.Get("exported").AsString());
     }
 
+    [Fact]
+    public void ShouldAddModuleFromClrFunction()
+    {
+        var received = new List<string>();
+        _engine.AddModule("imported-module", builder => builder
+            .ExportFunction("act_noargs", () => received.Add("act_noargs"))
+            .ExportFunction("act_args", args => received.Add($"act_args:{args[0].AsString()}"))
+            .ExportFunction("fn_noargs", () =>
+            {
+                received.Add("fn_noargs");
+                return "ret";
+            })
+            .ExportFunction("fn_args", args =>
+            {
+                received.Add($"fn_args:{args[0].AsString()}");
+                return "ret";
+            })
+        );
+        _engine.AddModule("my-module", @"
+import * as fns from 'imported-module';
+export const result = [fns.act_noargs(), fns.act_args('ok'), fns.fn_noargs(), fns.fn_args('ok')];");
+        var ns = _engine.ImportModule("my-module");
+
+        Assert.Equal(new[] { "act_noargs", "act_args:ok", "fn_noargs", "fn_args:ok" }, received.ToArray());
+        Assert.Equal(new[] { "undefined", "undefined", "ret", "ret" }, ns.Get("result").AsArray().Select(x => x.ToString()).ToArray());
+    }
+
     private class ImportedClass
     {
         public string Value { get; set; } = "hello world";
@@ -174,7 +236,6 @@ public class ModuleTests
         Assert.Equal("1 2", ns.Get("result").AsString());
     }
 
-    /* ECMAScript 2020 "export * as ns from"
     [Fact]
     public void ShouldAllowNamedStarExport()
     {
@@ -184,7 +245,6 @@ public class ModuleTests
 
         Assert.Equal(5, ns.Get("ns").Get("value1").AsNumber());
     }
-    */
 
     [Fact]
     public void ShouldAllowChaining()
@@ -201,8 +261,8 @@ public class ModuleTests
         Assert.Equal(-1, ns.Get("num").AsInteger());
     }
 
-    [Fact(Skip = "TODO re-enable in module fix branch")]
-    public void ShouldAllowLoadingMoreThanOnce()
+    [Fact]
+    public void ShouldImportOnlyOnce()
     {
         var called = 0;
         _engine.AddModule("imported-module", builder => builder.ExportFunction("count", args => called++));
@@ -210,12 +270,42 @@ public class ModuleTests
         _engine.ImportModule("my-module");
         _engine.ImportModule("my-module");
 
-        Assert.Equal(called, 1);
+        Assert.Equal(1, called);
+    }
+
+    [Fact]
+    public void ShouldAllowSelfImport()
+    {
+        _engine.AddModule("my-globals", @"export const globals = { counter: 0 };");
+        _engine.AddModule("my-module", @"
+import { globals } from 'my-globals';
+import {} from 'my-module';
+globals.counter++;
+export const count = globals.counter;
+");
+        var ns= _engine.ImportModule("my-module");
+
+        Assert.Equal(1, ns.Get("count").AsInteger());
+    }
+
+    [Fact]
+    public void ShouldAllowCyclicImport()
+    {
+        // https://tc39.es/ecma262/#sec-example-cyclic-module-record-graphs
+
+        _engine.AddModule("B", @"import { a } from 'A'; export const b = 'b';");
+        _engine.AddModule("A", @"import { b } from 'B'; export const a = 'a';");
+
+        var nsA = _engine.ImportModule("A");
+        var nsB = _engine.ImportModule("B");
+
+        Assert.Equal("a", nsA.Get("a").AsString());
+        Assert.Equal("b", nsB.Get("b").AsString());
     }
 
 #if(NET6_0_OR_GREATER)
 
-    [Fact(Skip = "TODO re-enable in module fix branch")]
+    [Fact]
     public void CanLoadModuleImportsFromFiles()
     {
         var engine = new Engine(options => options.EnableModules(GetBasePath()));
@@ -234,7 +324,7 @@ public class ModuleTests
 
         Assert.Equal("John Doe", result);
     }
-
+    
     private static string GetBasePath()
     {
         var assemblyPath = new Uri(typeof(ModuleTests).GetTypeInfo().Assembly.Location).LocalPath;

+ 74 - 47
Jint/Engine.Modules.cs

@@ -16,7 +16,7 @@ namespace Jint
     {
         internal IModuleLoader ModuleLoader { get; set; }
 
-        private readonly Dictionary<string, JsModule> _modules = new();
+        private readonly Dictionary<string, ModuleRecord> _modules = new();
         private readonly Dictionary<string, ModuleBuilder> _builders = new();
 
         /// <summary>
@@ -27,9 +27,7 @@ namespace Jint
             return _executionContexts?.GetActiveScriptOrModule();
         }
 
-        internal JsModule LoadModule(string specifier) => LoadModule(null, specifier);
-
-        internal JsModule LoadModule(string? referencingModuleLocation, string specifier)
+        internal ModuleRecord LoadModule(string? referencingModuleLocation, string specifier)
         {
             var moduleResolution = ModuleLoader.Resolve(referencingModuleLocation, specifier);
 
@@ -40,34 +38,44 @@ namespace Jint
 
             if (_builders.TryGetValue(specifier, out var moduleBuilder))
             {
-                var parsedModule = moduleBuilder.Parse();
-                module = new JsModule(this, _host.CreateRealm(), parsedModule, null, false);
-                // Early link is required because we need to bind values before returning
-                module.Link();
-                moduleBuilder.BindExportedValues(module);
-                _builders.Remove(specifier);
+                module = LoadFromBuilder(specifier, moduleBuilder, moduleResolution);
             }
             else
             {
-                var parsedModule = ModuleLoader.LoadModule(this, moduleResolution);
-                module = new JsModule(this, _host.CreateRealm(), parsedModule, moduleResolution.Uri?.LocalPath, false);
+                module = LoaderFromModuleLoader(moduleResolution);
             }
 
+            return module;
+        }
+
+        private CyclicModuleRecord LoadFromBuilder(string specifier, ModuleBuilder moduleBuilder, ResolvedSpecifier moduleResolution)
+        {
+            var parsedModule = moduleBuilder.Parse();
+            var module = new BuilderModuleRecord(this, Realm, parsedModule, null, false);
             _modules[moduleResolution.Key] = module;
+            moduleBuilder.BindExportedValues(module);
+            _builders.Remove(specifier);
+            return module;
+        }
 
+        private CyclicModuleRecord LoaderFromModuleLoader(ResolvedSpecifier moduleResolution)
+        {
+            var parsedModule = ModuleLoader.LoadModule(this, moduleResolution);
+            var module = new SourceTextModuleRecord(this, Realm, parsedModule, moduleResolution.Uri?.LocalPath, false);
+            _modules[moduleResolution.Key] = module;
             return module;
         }
 
-        public void AddModule(string specifier, string source)
+        public void AddModule(string specifier, string code)
         {
-            var moduleBuilder = new ModuleBuilder(this);
-            moduleBuilder.AddSource(source);
+            var moduleBuilder = new ModuleBuilder(this, specifier);
+            moduleBuilder.AddSource(code);
             AddModule(specifier, moduleBuilder);
         }
 
         public void AddModule(string specifier, Action<ModuleBuilder> buildModule)
         {
-            var moduleBuilder = new ModuleBuilder(this);
+            var moduleBuilder = new ModuleBuilder(this, specifier);
             buildModule(moduleBuilder);
             AddModule(specifier, moduleBuilder);
         }
@@ -79,63 +87,82 @@ namespace Jint
 
         public ObjectInstance ImportModule(string specifier)
         {
-            var moduleResolution = ModuleLoader.Resolve(null, specifier);
+            return ImportModule(specifier, null);
+        }
+
+        internal ObjectInstance ImportModule(string specifier, string? referencingModuleLocation)
+        {
+            var moduleResolution = ModuleLoader.Resolve(referencingModuleLocation, specifier);
 
             if (!_modules.TryGetValue(moduleResolution.Key, out var module))
             {
                 module = LoadModule(null, specifier);
             }
 
-            if (module.Status == ModuleStatus.Unlinked)
+            if (module is not CyclicModuleRecord cyclicModule)
             {
                 module.Link();
+                EvaluateModule(specifier, module);
             }
-
-            if (module.Status == ModuleStatus.Linked)
+            else if (cyclicModule.Status == ModuleStatus.Unlinked)
             {
-                var ownsContext = _activeEvaluationContext is null;
-                _activeEvaluationContext ??= new EvaluationContext(this);
-                JsValue evaluationResult;
                 try
                 {
-                    evaluationResult = module.Evaluate();
+                    cyclicModule.Link();
                 }
-                finally
+                catch (JavaScriptException ex)
                 {
-                    if (ownsContext)
-                    {
-                        _activeEvaluationContext = null;
-                    }
+                    if (ex.Location.Source == null)
+                        ex.SetLocation(new Location(new Position(), new Position(), specifier));
+                    throw;
                 }
 
-                if (evaluationResult == null)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise");
-                }
-                else if (evaluationResult is not PromiseInstance promise)
+                if (cyclicModule.Status == ModuleStatus.Linked)
                 {
-                    ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}");
+                    EvaluateModule(specifier, cyclicModule);
                 }
-                else if (promise.State == PromiseState.Rejected)
+
+                if (cyclicModule.Status != ModuleStatus.Evaluated)
                 {
-                    ExceptionHelper.ThrowJavaScriptException(this, promise.Value, new Completion(CompletionType.Throw, promise.Value, null, new Location(new Position(), new Position(), specifier)));
+                    ExceptionHelper.ThrowNotSupportedException($"Error while evaluating module: Module is in an invalid state: '{cyclicModule.Status}'");
                 }
-                else if (promise.State != PromiseState.Fulfilled)
+            }
+
+            RunAvailableContinuations();
+
+            return ModuleRecord.GetModuleNamespace(module);
+        }
+
+        private void EvaluateModule(string specifier, ModuleRecord cyclicModule)
+        {
+            var ownsContext = _activeEvaluationContext is null;
+            _activeEvaluationContext ??= new EvaluationContext(this);
+            JsValue evaluationResult;
+            try
+            {
+                evaluationResult = cyclicModule.Evaluate();
+            }
+            finally
+            {
+                if (ownsContext)
                 {
-                    ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a fulfilled promise: {promise.State}");
+                    _activeEvaluationContext = null;
                 }
             }
 
-            if (module.Status == ModuleStatus.Evaluated)
+            // This should instead be returned and resolved in ImportModule(specifier) only so Host.ImportModuleDynamically can use this promise
+            if (evaluationResult is not PromiseInstance promise)
             {
-                // TODO what about callstack and thrown exceptions?
-                RunAvailableContinuations(_eventLoop);
-
-                return JsModule.GetModuleNamespace(module);
+                ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}");
+            }
+            else if (promise.State == PromiseState.Rejected)
+            {
+                ExceptionHelper.ThrowJavaScriptException(this, promise.Value, new Completion(CompletionType.Throw, promise.Value, null, new Location(new Position(), new Position(), specifier)));
+            }
+            else if (promise.State != PromiseState.Fulfilled)
+            {
+                ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a fulfilled promise: {promise.State}");
             }
-
-            ExceptionHelper.ThrowNotSupportedException($"Error while evaluating module: Module is in an invalid state: '{module.Status}'");
-            return default;
         }
     }
 }

+ 6 - 7
Jint/Engine.cs

@@ -240,10 +240,10 @@ namespace Jint
         }
 
         public JsValue Evaluate(string source)
-            => Execute(source, DefaultParserOptions)._completionValue;
+            => Evaluate(source, DefaultParserOptions);
 
         public JsValue Evaluate(string source, ParserOptions parserOptions)
-            => Execute(source, parserOptions)._completionValue;
+            => Evaluate(new JavaScriptParser(source, parserOptions).ParseScript());
 
         public JsValue Evaluate(Script script)
             => Execute(script)._completionValue;
@@ -284,7 +284,7 @@ namespace Jint
                 }
 
                 // TODO what about callstack and thrown exceptions?
-                RunAvailableContinuations(_eventLoop);
+                RunAvailableContinuations();
 
                 _completionValue = result.GetValueOrDefault();
 
@@ -320,7 +320,7 @@ namespace Jint
             Action<JsValue> SettleWith(FunctionInstance settle) => value =>
             {
                 settle.Call(JsValue.Undefined, new[] {value});
-                RunAvailableContinuations(_eventLoop);
+                RunAvailableContinuations();
             };
 
             return new ManualPromise(promise, SettleWith(resolve), SettleWith(reject));
@@ -331,10 +331,9 @@ namespace Jint
             _eventLoop.Events.Enqueue(continuation);
         }
 
-
-        private static void RunAvailableContinuations(EventLoop loop)
+        internal void RunAvailableContinuations()
         {
-            var queue = loop.Events;
+            var queue = _eventLoop.Events;
 
             while (true)
             {

+ 25 - 13
Jint/EsprimaExtensions.cs

@@ -228,8 +228,11 @@ namespace Jint
                 }
                 else if (parameter is ClassDeclaration classDeclaration)
                 {
-                    parameter = classDeclaration.Id;
-                    continue;
+                    var name = classDeclaration.Id?.Name;
+                    if (name != null)
+                    {
+                        target.Add(name);
+                    }
                 }
 
                 break;
@@ -317,27 +320,35 @@ namespace Jint
                 case ExportAllDeclaration allDeclaration:
                     //Note: there is a pending PR for Esprima to support exporting an imported modules content as a namespace i.e. 'export * as ns from "mod"'
                     requestedModules.Add(allDeclaration.Source.StringValue!);
-                    exportEntries.Add(new(null, allDeclaration.Source.StringValue, "*", null));
+                    exportEntries.Add(new(allDeclaration.Exported?.GetModuleKey(), allDeclaration.Source.StringValue, "*", null));
                     break;
                 case ExportNamedDeclaration namedDeclaration:
-                    var specifiers = namedDeclaration.Specifiers;
+                    ref readonly var specifiers = ref namedDeclaration.Specifiers;
                     if (specifiers.Count == 0)
                     {
                         GetExportEntries(false, namedDeclaration.Declaration!, exportEntries, namedDeclaration.Source?.StringValue);
-
-                        if (namedDeclaration.Source is not null)
-                        {
-                            requestedModules.Add(namedDeclaration.Source.StringValue!);
-                        }
                     }
                     else
                     {
-                        foreach (var specifier in specifiers)
+                        for (var i = 0; i < specifiers.Count; i++)
                         {
-                            exportEntries.Add(new(specifier.Local.GetModuleKey(), namedDeclaration.Source?.StringValue, specifier.Exported.GetModuleKey(), null));
+                            var specifier = specifiers[i];
+                            if (namedDeclaration.Source != null)
+                            {
+                                exportEntries.Add(new(specifier.Exported.GetModuleKey(), namedDeclaration.Source.StringValue, specifier.Local.GetModuleKey(), null));
+                            }
+                            else
+                            {
+                                exportEntries.Add(new(specifier.Exported.GetModuleKey(), null, null, specifier.Local.GetModuleKey()));
+                            }
                         }
                     }
 
+                    if (namedDeclaration.Source is not null)
+                    {
+                        requestedModules.Add(namedDeclaration.Source.StringValue!);
+                    }
+
                     break;
             }
         }
@@ -387,9 +398,10 @@ namespace Jint
 
                     break;
                 case VariableDeclaration variableDeclaration:
-                    var declarators = variableDeclaration.Declarations;
-                    foreach (var declarator in declarators)
+                    ref readonly var declarators = ref variableDeclaration.Declarations;
+                    for (var i = 0; i < declarators.Count; i++)
                     {
+                        var declarator = declarators[i];
                         var varName = declarator.Id.As<Identifier>()?.Name;
                         if (varName is not null)
                         {

+ 16 - 9
Jint/HoistingScope.cs

@@ -1,5 +1,4 @@
 using System.Collections.Generic;
-using System.Linq;
 using Esprima.Ast;
 using Jint.Runtime.Modules;
 
@@ -30,7 +29,7 @@ namespace Jint
         }
 
         public static HoistingScope GetProgramLevelDeclarations(
-            Script script,
+            Program script,
             bool collectVarNames = false,
             bool collectLexicalNames = false)
         {
@@ -172,14 +171,22 @@ namespace Jint
                         }
                         else
                         {
-                            var ie = importEntries.First(x => x.LocalName == ee.LocalName);
-                            if (ie.ImportName == "*")
+                            for (var j = 0; j < importEntries!.Count; j++)
                             {
-                                localExportEntries.Add(ee);
-                            }
-                            else
-                            {
-                                indirectExportEntries.Add(new(ee.ExportName, ie.ModuleRequest, ie.ImportName, null));
+                                var ie = importEntries[j];
+                                if (ie.LocalName == ee.LocalName)
+                                {
+                                    if (ie.ImportName == "*")
+                                    {
+                                        localExportEntries.Add(ee);
+                                    }
+                                    else
+                                    {
+                                        indirectExportEntries.Add(new(ee.ExportName, ie.ModuleRequest, ie.ImportName, null));
+                                    }
+
+                                    break;
+                                }
                             }
                         }
                     }

+ 57 - 7
Jint/ModuleBuilder.cs

@@ -5,6 +5,7 @@ using System.Collections.Generic;
 using Esprima;
 using Esprima.Ast;
 using Jint.Native;
+using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Modules;
 
@@ -13,12 +14,16 @@ namespace Jint;
 public sealed class ModuleBuilder
 {
     private readonly Engine _engine;
+    private readonly string _specifier;
     private readonly List<string> _sourceRaw = new();
     private readonly Dictionary<string, JsValue> _exports = new();
+    private readonly ParserOptions _options;
 
-    public ModuleBuilder(Engine engine)
+    internal ModuleBuilder(Engine engine, string specifier)
     {
         _engine = engine;
+        _specifier = specifier;
+        _options = new ParserOptions(specifier);
     }
 
     public ModuleBuilder AddSource(string code)
@@ -65,23 +70,68 @@ public sealed class ModuleBuilder
 
     public ModuleBuilder ExportFunction(string name, Func<JsValue[], JsValue> fn)
     {
-        _exports.Add(name, new ClrFunctionInstance(_engine, name, (@this, args) => fn(args)));
+        _exports.Add(name, new ClrFunctionInstance(_engine, name, (_, args) => fn(args)));
+        return this;
+    }
+
+    public ModuleBuilder ExportFunction(string name, Func<JsValue> fn)
+    {
+        _exports.Add(name, new ClrFunctionInstance(_engine, name, (_, _) => fn()));
+        return this;
+    }
+
+    public ModuleBuilder ExportFunction(string name, Action<JsValue[]> fn)
+    {
+        _exports.Add(name, new ClrFunctionInstance(_engine, name, (_, args) =>
+        {
+            fn(args);
+            return JsValue.Undefined;
+        }));
+        return this;
+    }
+
+    public ModuleBuilder ExportFunction(string name, Action fn)
+    {
+        _exports.Add(name, new ClrFunctionInstance(_engine, name, (_, _) =>
+        {
+            fn();
+            return JsValue.Undefined;
+        }));
+        return this;
+    }
+
+    public ModuleBuilder WithOptions(Action<ParserOptions> configure)
+    {
+        configure(_options);
         return this;
     }
 
     internal Module Parse()
     {
-        if (_sourceRaw.Count > 0)
+        if (_sourceRaw.Count <= 0)
         {
-            return new JavaScriptParser(_sourceRaw.Count == 1 ? _sourceRaw[0] : string.Join(Environment.NewLine, _sourceRaw)).ParseModule();
+            return new Module(NodeList.Create(Array.Empty<Statement>()));
         }
-        else
+
+        var javaScriptParser = new JavaScriptParser(_sourceRaw.Count == 1 ? _sourceRaw[0] : string.Join(Environment.NewLine, _sourceRaw), _options);
+
+        try
         {
-            return new Module(NodeList.Create(Array.Empty<Statement>()));
+            return javaScriptParser.ParseModule();
+        }
+        catch (ParserException ex)
+        {
+            ExceptionHelper.ThrowSyntaxError(_engine.Realm, $"Error while loading module: error in module '{_specifier}': {ex.Error}");
+            return null!;
+        }
+        catch (Exception)
+        {
+            ExceptionHelper.ThrowJavaScriptException(_engine, $"Could not load module {_specifier}", Completion.Empty());
+            return null!;
         }
     }
 
-    internal void BindExportedValues(JsModule module)
+    internal void BindExportedValues(BuilderModuleRecord module)
     {
         foreach (var export in _exports)
         {

+ 1 - 1
Jint/Native/Array/ArrayPrototype.cs

@@ -1458,7 +1458,7 @@ namespace Jint.Native.Array
             return element;
         }
 
-        private sealed class ArrayComparer : IComparer<JsValue>
+        internal sealed class ArrayComparer : IComparer<JsValue>
         {
             /// <summary>
             /// Default instance without any compare function.

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

@@ -35,11 +35,11 @@ namespace Jint.Native.Function
         }
 
         public ClassDefinition(
-            Identifier? className,
+            string? className,
             Expression? superClass,
             ClassBody body)
         {
-            _className = className?.Name;
+            _className = className;
             _superClass = superClass;
             _body = body;
         }

+ 1 - 2
Jint/Options.cs

@@ -112,8 +112,7 @@ namespace Jint
                         (thisObj, arguments) =>
                         {
                             var specifier = TypeConverter.ToString(arguments.At(0));
-                            var module = engine.LoadModule(specifier);
-                            return JsModule.GetModuleNamespace(module);
+                            return engine.ImportModule(specifier);
                         }),
                     PropertyFlag.AllForbidden));
             }

+ 15 - 3
Jint/Runtime/Environments/ModuleEnvironmentRecord.cs

@@ -16,18 +16,27 @@ internal sealed class ModuleEnvironmentRecord : DeclarativeEnvironmentRecord
     {
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-environment-records-getthisbinding
+    /// </summary>
     public override JsValue GetThisBinding()
     {
         return Undefined;
     }
 
-    public void CreateImportBinding(string importName, JsModule module, string name)
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-createimportbinding
+    /// </summary>
+    public void CreateImportBinding(string importName, ModuleRecord module, string name)
     {
         _hasBindings = true;
         _importBindings[importName] = new IndirectBinding(module, name);
+        CreateImmutableBindingAndInitialize(importName, true, JsValue.Undefined);
     }
 
-    // https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s
+    /// </summary>
     public override JsValue GetBindingValue(string name, bool strict)
     {
         if (_importBindings.TryGetValue(name, out var indirectBinding))
@@ -50,7 +59,10 @@ internal sealed class ModuleEnvironmentRecord : DeclarativeEnvironmentRecord
         return base.TryGetBinding(name, strict, out binding, out value);
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-environment-records-hasthisbinding
+    /// </summary>
     public override bool HasThisBinding() => true;
 
-    private readonly record struct IndirectBinding(JsModule Module, string BindingName);
+    private readonly record struct IndirectBinding(ModuleRecord Module, string BindingName);
 }

+ 25 - 22
Jint/Runtime/Host.cs

@@ -1,3 +1,5 @@
+#nullable enable
+
 using Jint.Native;
 using Jint.Native.Global;
 using Jint.Native.Object;
@@ -11,7 +13,20 @@ namespace Jint.Runtime
 {
     public class Host
     {
-        protected Engine Engine { get; private set; }
+        private Engine? _engine;
+
+        protected Engine Engine
+        {
+            get
+            {
+                if (_engine is null)
+                {
+                    ExceptionHelper.ThrowInvalidOperationException("Initialize has not been called");
+                }
+                return _engine!;
+            }
+            private set => _engine = value;
+        }
 
         /// <summary>
         /// Initializes the host.
@@ -93,8 +108,6 @@ namespace Jint.Runtime
         /// <summary>
         /// https://tc39.es/ecma262/#sec-hostensurecancompilestrings
         /// </summary>
-        /// <param name="callerRealm"></param>
-        /// <param name="evalRealm"></param>
         public virtual void EnsureCanCompileStrings(Realm callerRealm, Realm evalRealm)
         {
         }
@@ -102,54 +115,44 @@ namespace Jint.Runtime
         /// <summary>
         /// https://tc39.es/ecma262/#sec-hostresolveimportedmodule
         /// </summary>
-        /// <param name="referencingModule"></param>
-        /// <param name="specifier"></param>
-        /// <returns></returns>
-        protected internal virtual JsModule ResolveImportedModule(JsModule referencingModule, string specifier)
+        internal virtual ModuleRecord ResolveImportedModule(IScriptOrModule? referencingScriptOrModule, string specifier)
         {
-            return Engine.LoadModule(referencingModule.Location, specifier);
+            return Engine.LoadModule(referencingScriptOrModule?.Location, specifier);
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-hostimportmoduledynamically
         /// </summary>
-        /// <param name="referencingModule"></param>
-        /// <param name="specifier"></param>
-        /// <param name="promiseCapability"></param>
-        internal virtual void ImportModuleDynamically(JsModule referencingModule, string specifier, PromiseCapability promiseCapability)
+        internal virtual void ImportModuleDynamically(IScriptOrModule? referencingModule, string specifier, PromiseCapability promiseCapability)
         {
             var promise = Engine.RegisterPromise();
 
             try
             {
-                Engine.LoadModule(referencingModule.Location, specifier);
+                // This should instead return the PromiseInstance returned by ModuleRecord.Evaluate (currently done in Engine.EvaluateModule), but until we have await this will do.
+                Engine.ImportModule(specifier, referencingModule?.Location);
                 promise.Resolve(JsValue.Undefined);
-
             }
             catch (JavaScriptException ex)
             {
                 promise.Reject(ex.Error);
             }
 
-            FinishDynamicImport(referencingModule, specifier, promiseCapability, (PromiseInstance)promise.Promise);
+            FinishDynamicImport(referencingModule, specifier, promiseCapability, (PromiseInstance) promise.Promise);
         }
 
         /// <summary>
         /// https://tc39.es/ecma262/#sec-finishdynamicimport
         /// </summary>
-        /// <param name="referencingModule"></param>
-        /// <param name="specifier"></param>
-        /// <param name="promiseCapability"></param>
-        /// <param name="innerPromise"></param>
-        internal virtual void FinishDynamicImport(JsModule referencingModule, string specifier, PromiseCapability promiseCapability, PromiseInstance innerPromise)
+        internal virtual void FinishDynamicImport(IScriptOrModule? referencingModule, string specifier, PromiseCapability promiseCapability, PromiseInstance innerPromise)
         {
             var onFulfilled = new ClrFunctionInstance(Engine, "", (thisObj, args) =>
             {
                 var moduleRecord = ResolveImportedModule(referencingModule, specifier);
                 try
                 {
-                    var ns = JsModule.GetModuleNamespace(moduleRecord);
-                    promiseCapability.Resolve.Call(JsValue.Undefined, new[] { ns });
+                    var ns = ModuleRecord.GetModuleNamespace(moduleRecord);
+                    promiseCapability.Resolve.Call(JsValue.Undefined, new JsValue[] { ns });
                 }
                 catch (JavaScriptException ex)
                 {

+ 2 - 2
Jint/Runtime/IScriptOrModule.Extensions.cs

@@ -7,9 +7,9 @@ namespace Jint.Runtime;
 
 internal static class ScriptOrModuleExtensions
 {
-    public static JsModule AsModule(this IScriptOrModule? scriptOrModule, Engine engine, Location location)
+    public static ModuleRecord AsModule(this IScriptOrModule? scriptOrModule, Engine engine, Location location)
     {
-        var module = scriptOrModule as JsModule;
+        var module = scriptOrModule as ModuleRecord;
         if (module == null)
         {
             ExceptionHelper.ThrowSyntaxError(engine.Realm, "Cannot use import/export statements outside a module", location);

+ 1 - 0
Jint/Runtime/IScriptOrModule.cs

@@ -2,4 +2,5 @@ namespace Jint.Runtime;
 
 internal interface IScriptOrModule
 {
+    public string Location { get; }
 }

+ 2 - 1
Jint/Runtime/Interpreter/Expressions/JintAssignmentExpression.cs

@@ -362,6 +362,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 return completion ?? SetValue(context);
             }
 
+            // https://262.ecma-international.org/5.1/#sec-11.13.1
             private ExpressionResult SetValue(EvaluationContext context)
             {
                 // slower version
@@ -421,4 +422,4 @@ namespace Jint.Runtime.Interpreter.Expressions
             }
         }
     }
-}
+}

+ 1 - 1
Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs

@@ -9,7 +9,7 @@ namespace Jint.Runtime.Interpreter.Expressions
 
         public JintClassExpression(ClassExpression expression) : base(expression)
         {
-            _classDefinition = new ClassDefinition(expression.Id, expression.SuperClass, expression.Body);
+            _classDefinition = new ClassDefinition(expression.Id?.Name, expression.SuperClass, expression.Body);
         }
 
         protected override ExpressionResult EvaluateInternal(EvaluationContext context)

+ 1 - 0
Jint/Runtime/Interpreter/Expressions/JintExpression.cs

@@ -193,6 +193,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 Nodes.TemplateLiteral => new JintTemplateLiteralExpression((TemplateLiteral) expression),
                 Nodes.TaggedTemplateExpression => new JintTaggedTemplateExpression((TaggedTemplateExpression) expression),
                 Nodes.ClassExpression => new JintClassExpression((ClassExpression) expression),
+                Nodes.Import => new JintImportExpression((Import) expression),
                 Nodes.Super => new JintSuperExpression((Super) expression),
                 Nodes.MetaProperty => new JintMetaPropertyExpression((MetaProperty) expression),
                 Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression

+ 39 - 0
Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs

@@ -0,0 +1,39 @@
+#nullable enable
+
+using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Promise;
+
+namespace Jint.Runtime.Interpreter.Expressions;
+
+internal sealed class JintImportExpression : JintExpression
+{
+    private JintExpression _importExpression;
+
+    public JintImportExpression(Import expression) : base(expression)
+    {
+        _initialized = false;
+        _importExpression = null!;
+    }
+
+    protected override void Initialize(EvaluationContext context)
+    {
+        var expression = ((Import) _expression).Source;
+        _importExpression = Build(context.Engine, expression!);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-import-calls
+    /// </summary>
+    protected override ExpressionResult EvaluateInternal(EvaluationContext context)
+    {
+        var referencingScriptOrModule = context.Engine.GetActiveScriptOrModule();
+        var argRef = _importExpression.Evaluate(context);
+        var specifier = context.Engine.GetValue(argRef.Value); //.UnwrapIfPromise();
+        var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
+        var specifierString = TypeConverter.ToString(specifier);
+        context.Engine._host.ImportModuleDynamically(referencingScriptOrModule, specifierString, promiseCapability);
+        context.Engine.RunAvailableContinuations();
+        return NormalCompletion(promiseCapability.PromiseInstance);
+    }
+}

+ 14 - 4
Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs

@@ -111,6 +111,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                 }
 
                 case UnaryOperator.Delete:
+                    // https://262.ecma-international.org/5.1/#sec-11.4.1
                     if (_argument.Evaluate(context).Value is not Reference r)
                     {
                         return JsBoolean.True;
@@ -127,6 +128,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                         return JsBoolean.True;
                     }
 
+                    var referencedName = r.GetReferencedName();
                     if (r.IsPropertyReference())
                     {
                         if (r.IsSuperReference())
@@ -135,10 +137,18 @@ namespace Jint.Runtime.Interpreter.Expressions
                         }
 
                         var o = TypeConverter.ToObject(engine.Realm, r.GetBase());
-                        var deleteStatus = o.Delete(r.GetReferencedName());
-                        if (!deleteStatus && r.IsStrictReference())
+                        var deleteStatus = o.Delete(referencedName);
+                        if (!deleteStatus)
                         {
-                            ExceptionHelper.ThrowTypeError(engine.Realm, $"Cannot delete property '{r.GetReferencedName()}' of {o}");
+                            if (r.IsStrictReference())
+                            {
+                                ExceptionHelper.ThrowTypeError(engine.Realm, $"Cannot delete property '{referencedName}' of {o}");
+                            }
+
+                            if (StrictModeScope.IsStrictModeCode && !r.GetBase().AsObject().GetProperty(referencedName).Configurable)
+                            {
+                                ExceptionHelper.ThrowTypeError(engine.Realm, $"Cannot delete property '{referencedName}' of {o}");
+                            }
                         }
 
                         engine._referencePool.Return(r);
@@ -151,7 +161,7 @@ namespace Jint.Runtime.Interpreter.Expressions
                     }
 
                     var bindings = r.GetBase().TryCast<EnvironmentRecord>();
-                    var property = r.GetReferencedName();
+                    var property = referencedName;
                     engine._referencePool.Return(r);
 
                     return bindings.DeleteBinding(property.ToString()) ? JsBoolean.True : JsBoolean.False;

+ 1 - 1
Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs

@@ -11,7 +11,7 @@ namespace Jint.Runtime.Interpreter.Statements
 
         public JintClassDeclarationStatement(ClassDeclaration classDeclaration) : base(classDeclaration)
         {
-            _classDefinition = new ClassDefinition(className: classDeclaration.Id, classDeclaration.SuperClass, classDeclaration.Body);
+            _classDefinition = new ClassDefinition(className: classDeclaration.Id?.Name, classDeclaration.SuperClass, classDeclaration.Body);
         }
 
         protected override Completion ExecuteInternal(EvaluationContext context)

+ 73 - 8
Jint/Runtime/Interpreter/Statements/JintExportDefaultDeclaration.cs

@@ -1,13 +1,20 @@
 #nullable enable
 
 using Esprima.Ast;
+using Jint.Native;
+using Jint.Native.Function;
+using Jint.Native.Object;
+using Jint.Runtime.Environments;
 using Jint.Runtime.Interpreter.Expressions;
 
 namespace Jint.Runtime.Interpreter.Statements;
 
 internal sealed class JintExportDefaultDeclaration : JintStatement<ExportDefaultDeclaration>
 {
-    private JintExpression? _init;
+    private ClassDefinition? _classDefinition;
+    private JintFunctionDeclarationStatement? _functionDeclaration;
+    private JintExpression? _assignmentExpression;
+    private JintExpression? _simpleExpression;
 
     public JintExportDefaultDeclaration(ExportDefaultDeclaration statement) : base(statement)
     {
@@ -15,17 +22,75 @@ internal sealed class JintExportDefaultDeclaration : JintStatement<ExportDefault
 
     protected override void Initialize(EvaluationContext context)
     {
-        _init = JintExpression.Build(context.Engine, (Expression)_statement.Declaration);
+        if (_statement.Declaration is ClassDeclaration classDeclaration)
+        {
+            _classDefinition = new ClassDefinition(className: classDeclaration.Id?.Name, classDeclaration.SuperClass, classDeclaration.Body);
+        }
+        else if (_statement.Declaration is FunctionDeclaration functionDeclaration)
+        {
+            _functionDeclaration = new JintFunctionDeclarationStatement(functionDeclaration);
+        }
+        else if (_statement.Declaration is AssignmentExpression assignmentExpression)
+        {
+            _assignmentExpression = JintAssignmentExpression.Build(context.Engine, assignmentExpression);
+        }
+        else
+        {
+            _simpleExpression = JintExpression.Build(context.Engine, (Expression) _statement.Declaration);
+        }
     }
 
-    // https://tc39.es/ecma262/#sec-exports-runtime-semantics-evaluation
+    /// <summary>
+    ///  https://tc39.es/ecma262/#sec-exports-runtime-semantics-evaluation
+    /// </summary>
     protected override Completion ExecuteInternal(EvaluationContext context)
     {
-        var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location);
-
-        var completion = _init?.GetValue(context) ?? Completion.Empty();
-        module._environment.CreateImmutableBindingAndInitialize("*default*", true, completion.Value);
+        var env = context.Engine.ExecutionContext.LexicalEnvironment;
+        JsValue value;
+        if (_classDefinition is not null)
+        {
+            value = _classDefinition.BuildConstructor(context, env);
+            var classBinding = _classDefinition._className;
+            if (classBinding != null)
+            {
+                env.CreateMutableBinding(classBinding);
+                env.InitializeBinding(classBinding, value);
+            }
+        }     
+        else if (_functionDeclaration is not null)
+        {
+            value = _functionDeclaration.Execute(context).GetValueOrDefault();
+        }
+        else if (_assignmentExpression is not null)
+        {
+            value = _assignmentExpression.GetValue(context).GetValueOrDefault();
+        }
+        else
+        {
+            value = _simpleExpression!.GetValue(context).GetValueOrDefault();
+        }
 
+        if (value is ObjectInstance oi && !oi.HasOwnProperty("name"))
+        {
+            oi.SetFunctionName("default");
+        }
+        
+        env.InitializeBinding("*default*", value);
         return Completion.Empty();
     }
-}
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-initializeboundname
+    /// </summary>
+    private void InitializeBoundName(string name, JsValue value, EnvironmentRecord? environment)
+    {
+        if (environment is not null)
+        {
+            environment.InitializeBinding(name, value);
+        }
+        else
+        {
+            ExceptionHelper.ThrowNotImplementedException();
+        }
+    }
+}

+ 0 - 42
Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs

@@ -10,12 +10,6 @@ internal sealed class JintExportNamedDeclaration : JintStatement<ExportNamedDecl
 {
     private JintExpression? _declarationExpression;
     private JintStatement? _declarationStatement;
-    private ExportedSpecifier[]? _specifiers;
-
-    private sealed record ExportedSpecifier(
-        JintExpression Local,
-        JintExpression Exported
-    );
 
     public JintExportNamedDeclaration(ExportNamedDeclaration statement) : base(statement)
     {
@@ -38,21 +32,6 @@ internal sealed class JintExportNamedDeclaration : JintStatement<ExportNamedDecl
                     break;
             }
         }
-
-        if (_statement.Specifiers.Count > 0)
-        {
-            _specifiers = new ExportedSpecifier[_statement.Specifiers.Count];
-            ref readonly var statementSpecifiers = ref _statement.Specifiers;
-            for (var i = 0; i < statementSpecifiers.Count; i++)
-            {
-                var statementSpecifier = statementSpecifiers[i];
-
-                _specifiers[i] = new ExportedSpecifier(
-                    Local: JintExpression.Build(context.Engine, statementSpecifier.Local),
-                    Exported: JintExpression.Build(context.Engine, statementSpecifier.Exported)
-                );
-            }
-        }
     }
 
     /// <summary>
@@ -60,27 +39,6 @@ internal sealed class JintExportNamedDeclaration : JintStatement<ExportNamedDecl
     /// </summary>
     protected override Completion ExecuteInternal(EvaluationContext context)
     {
-        var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location);
-
-        if (_specifiers != null)
-        {
-            foreach (var specifier in _specifiers)
-            {
-                if (specifier.Local is not JintIdentifierExpression local || specifier.Exported is not JintIdentifierExpression exported)
-                {
-                    ExceptionHelper.ThrowSyntaxError(context.Engine.Realm, "", context.LastSyntaxNode.Location);
-                    return default;
-                }
-
-                var localKey = local._expressionName.Key.Name;
-                var exportedKey = exported._expressionName.Key.Name;
-                if (localKey != exportedKey)
-                {
-                    module._environment.CreateImportBinding(exportedKey, module, localKey);
-                }
-            }
-        }
-
         if (_declarationStatement != null)
         {
             _declarationStatement.Execute(context);

+ 3 - 9
Jint/Runtime/Interpreter/Statements/JintImportDeclaration.cs

@@ -1,7 +1,6 @@
 #nullable enable
 
 using Esprima.Ast;
-using Jint.Native.Promise;
 
 namespace Jint.Runtime.Interpreter.Statements;
 
@@ -17,13 +16,8 @@ internal sealed class JintImportDeclaration : JintStatement<ImportDeclaration>
 
     protected override Completion ExecuteInternal(EvaluationContext context)
     {
-        var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location);
-        var specifier = _statement.Source.StringValue;
-        var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise);
-        var specifierString = TypeConverter.ToString(specifier);
-
-        // TODO: This comment was in @lahma's code: 6.IfAbruptRejectPromise(specifierString, promiseCapability);
-        context.Engine._host.ImportModuleDynamically(module, specifierString, promiseCapability);
-        return NormalCompletion(promiseCapability.PromiseInstance);
+        // just to ensure module context or valid
+        context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location);
+        return Completion.Empty();
     }
 }

+ 46 - 0
Jint/Runtime/Modules/BuilderModuleRecord.cs

@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using Esprima.Ast;
+using Jint.Native;
+
+namespace Jint.Runtime.Modules;
+
+/// <summary>
+/// This is a custom ModuleRecord implementation for dynamically built modules using <see cref="ModuleBuilder"/>
+/// </summary>
+internal sealed class BuilderModuleRecord : SourceTextModuleRecord
+{
+    private List<KeyValuePair<string, JsValue>> _exportBuilderDeclarations = new();
+
+    internal BuilderModuleRecord(Engine engine, Realm realm, Module source, string location, bool async)
+        : base(engine, realm, source, location, async)
+    {
+    }
+
+    internal void BindExportedValue(string name, JsValue value)
+    {
+        if (_environment != null)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Cannot bind exported values after the environment has been initialized");
+        }
+
+        _exportBuilderDeclarations ??= new();
+        _exportBuilderDeclarations.Add(new KeyValuePair<string, JsValue>(name, value));
+    }
+
+    protected override void InitializeEnvironment()
+    {
+        base.InitializeEnvironment();
+
+        if (_exportBuilderDeclarations != null)
+        {
+            for (var i = 0; i < _exportBuilderDeclarations.Count; i++)
+            {
+                var d = _exportBuilderDeclarations[i];
+                _environment.CreateImmutableBindingAndInitialize(d.Key, true, d.Value);
+                _localExportEntries.Add(new ExportEntry(d.Key, null, null, d.Key));
+            }
+
+            _exportBuilderDeclarations.Clear();
+        }
+    }
+}

+ 588 - 0
Jint/Runtime/Modules/CyclicModuleRecord.cs

@@ -0,0 +1,588 @@
+using System;
+using Esprima.Ast;
+using System.Collections.Generic;
+using Jint.Native;
+using Jint.Native.Promise;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Interop;
+
+namespace Jint.Runtime.Modules;
+
+#pragma warning disable CS0649 // never assigned to, waiting for new functionalities in spec
+
+internal sealed record ResolvedBinding(ModuleRecord Module, string BindingName)
+{
+    internal static ResolvedBinding Ambiguous => new(null, "ambiguous");
+}
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-cyclic-module-records
+/// </summary>
+public abstract class CyclicModuleRecord : ModuleRecord
+{
+    private Completion? _evalError;
+    private int _dfsIndex;
+    private int _dfsAncestorIndex;
+    protected HashSet<string> _requestedModules;
+    private CyclicModuleRecord _cycleRoot;
+    protected bool _hasTLA;
+    private bool _asyncEvaluation;
+    private PromiseCapability _topLevelCapability;
+    private List<CyclicModuleRecord> _asyncParentModules;
+    private int _asyncEvalOrder;
+    private int _pendingAsyncDependencies;
+
+    internal JsValue _evalResult;
+
+    internal CyclicModuleRecord(Engine engine, Realm realm, Module source, string location, bool async) : base(engine, realm, location)
+    {
+    }
+
+    internal ModuleStatus Status { get; private set; }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-moduledeclarationlinking
+    /// </summary>
+    public override void Link()
+    {
+        if (Status is ModuleStatus.Linking or ModuleStatus.Evaluating)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module is already either linking or evaluating");
+        }
+
+        var stack = new Stack<CyclicModuleRecord>();
+
+        try
+        {
+            InnerModuleLinking(stack, 0);
+        }
+        catch
+        {
+            foreach (var m in stack)
+            {
+                m._environment = null;
+
+                if (m.Status != ModuleStatus.Linking)
+                {
+                    ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module should be linking after abrupt completion");
+                }
+
+                m.Status = ModuleStatus.Unlinked;
+                m._dfsIndex = -1;
+                m._dfsAncestorIndex = -1;
+            }
+
+            if (Status != ModuleStatus.Unlinked)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while processing abrupt completion of module link: Module should be unlinked after cleanup");
+            }
+
+            throw;
+        }
+
+        if (Status is not (ModuleStatus.Linked or ModuleStatus.EvaluatingAsync or ModuleStatus.Evaluated))
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module is neither linked, evaluating-async or evaluated");
+        }
+
+        if (stack.Count > 0)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: One or more modules were not linked");
+        }
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-moduleevaluation
+    /// </summary>
+    public override JsValue Evaluate()
+    {
+        var module = this;
+
+        if (module.Status != ModuleStatus.Linked &&
+            module.Status != ModuleStatus.EvaluatingAsync &&
+            module.Status != ModuleStatus.Evaluated)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+        }
+
+        if (module.Status is ModuleStatus.EvaluatingAsync or ModuleStatus.Evaluated)
+        {
+            module = module._cycleRoot;
+        }
+
+        if (module._topLevelCapability is not null)
+        {
+            return module._topLevelCapability.PromiseInstance;
+        }
+
+        var stack = new Stack<CyclicModuleRecord>();
+        var capability = PromiseConstructor.NewPromiseCapability(_engine, _realm.Intrinsics.Promise);
+        var asyncEvalOrder = 0;
+        module._topLevelCapability = capability;
+
+        var result = module.InnerModuleEvaluation(stack, 0, ref asyncEvalOrder);
+
+        if (result.Type != CompletionType.Normal)
+        {
+            foreach (var m in stack)
+            {
+                m.Status = ModuleStatus.Evaluated;
+                m._evalError = result;
+            }
+
+            capability.Reject.Call(Undefined, new[] { result.Value });
+        }
+        else
+        {
+            if (module.Status != ModuleStatus.EvaluatingAsync && module.Status != ModuleStatus.Evaluated)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+
+            if (module._evalError is not null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+
+            if (!module._asyncEvaluation)
+            {
+                if (module.Status != ModuleStatus.Evaluated)
+                {
+                    ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+                }
+
+                capability.Resolve.Call(Undefined, Array.Empty<JsValue>());
+            }
+
+            if (stack.Count > 0)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+        }
+
+        return capability.PromiseInstance;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-InnerModuleLinking
+    /// </summary>
+    protected internal override int InnerModuleLinking(Stack<CyclicModuleRecord> stack, int index)
+    {
+        if (Status is
+            ModuleStatus.Linking or
+            ModuleStatus.Linked or
+            ModuleStatus.EvaluatingAsync or
+            ModuleStatus.Evaluated)
+        {
+            return index;
+        }
+
+        if (Status != ModuleStatus.Unlinked)
+        {
+            ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Module in an invalid state: {Status}");
+        }
+
+        Status = ModuleStatus.Linking;
+        _dfsIndex = index;
+        _dfsAncestorIndex = index;
+        index++;
+        stack.Push(this);
+
+        foreach (var required in _requestedModules)
+        {
+            var requiredModule = _engine._host.ResolveImportedModule(this, required);
+
+            index = requiredModule.InnerModuleLinking(stack, index);
+
+            if (requiredModule is not CyclicModuleRecord requiredCyclicModule)
+            {
+                continue;
+            }
+
+            if (requiredCyclicModule.Status is not (
+                ModuleStatus.Linking or
+                ModuleStatus.Linked or
+                ModuleStatus.EvaluatingAsync or
+                ModuleStatus.Evaluated))
+            {
+                ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Required module is in an invalid state: {requiredCyclicModule.Status}");
+            }
+
+            if ((requiredCyclicModule.Status == ModuleStatus.Linking) == !stack.Contains(requiredCyclicModule))
+            {
+                ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Required module is in an invalid state: {requiredCyclicModule.Status}");
+            }
+
+            if (requiredCyclicModule.Status == ModuleStatus.Linking)
+            {
+                _dfsAncestorIndex = Math.Min(_dfsAncestorIndex, requiredCyclicModule._dfsAncestorIndex);
+            }
+        }
+
+        InitializeEnvironment();
+
+        if (StackReferenceCount(stack) != 1)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Recursive dependency detected");
+        }
+
+        if (_dfsAncestorIndex > _dfsIndex)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Recursive dependency detected");
+        }
+
+        if (_dfsIndex == _dfsAncestorIndex)
+        {
+            while (true)
+            {
+                var requiredModule = stack.Pop();
+                requiredModule.Status = ModuleStatus.Linked;
+                if (requiredModule == this)
+                {
+                    break;
+                }
+            }
+        }
+
+        return index;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-innermoduleevaluation
+    /// </summary>
+    protected internal override Completion InnerModuleEvaluation(Stack<CyclicModuleRecord> stack, int index, ref int asyncEvalOrder)
+    {
+        if (Status is ModuleStatus.EvaluatingAsync or ModuleStatus.Evaluated)
+        {
+            if (_evalError is null)
+            {
+                return new Completion(CompletionType.Normal, index, null, default);
+            }
+
+            return _evalError.Value;
+        }
+
+        if (Status == ModuleStatus.Evaluating)
+        {
+            return new Completion(CompletionType.Normal, index, null, default);
+        }
+
+        if (Status != ModuleStatus.Linked)
+        {
+            ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module is in an invalid state: {Status}");
+        }
+
+        Status = ModuleStatus.Evaluating;
+        _dfsIndex = index;
+        _dfsAncestorIndex = index;
+        _pendingAsyncDependencies = 0;
+        index++;
+        stack.Push(this);
+
+        foreach (var required in _requestedModules)
+        {
+            var requiredModule = _engine._host.ResolveImportedModule(this, required);
+
+            var result = requiredModule.InnerModuleEvaluation(stack, index, ref asyncEvalOrder);
+            if (result.Type != CompletionType.Normal)
+            {
+                return result;
+            }
+
+            index = TypeConverter.ToInt32(result.Value);
+
+            if (requiredModule is not CyclicModuleRecord requiredCyclicModule)
+            {
+                ExceptionHelper.ThrowNotImplementedException($"Resolving modules of type {requiredModule.GetType()} is not implemented");
+                continue;
+            }
+
+            if (requiredCyclicModule.Status != ModuleStatus.Evaluating &&
+                requiredCyclicModule.Status != ModuleStatus.EvaluatingAsync &&
+                requiredCyclicModule.Status != ModuleStatus.Evaluated)
+            {
+                ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module is in an invalid state: {requiredCyclicModule.Status}");
+            }
+
+            if (requiredCyclicModule.Status == ModuleStatus.Evaluating && !stack.Contains(requiredCyclicModule))
+            {
+                ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module is in an invalid state: {requiredCyclicModule.Status}");
+            }
+
+            if (requiredCyclicModule.Status == ModuleStatus.Evaluating)
+            {
+                _dfsAncestorIndex = Math.Min(_dfsAncestorIndex, requiredCyclicModule._dfsAncestorIndex);
+            }
+            else
+            {
+                requiredCyclicModule = requiredCyclicModule._cycleRoot;
+                if (requiredCyclicModule.Status is not (ModuleStatus.EvaluatingAsync or ModuleStatus.Evaluated))
+                {
+                    ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+                }
+
+                if (requiredCyclicModule._evalError != null)
+                {
+                    return requiredCyclicModule._evalError.Value;
+                }
+            }
+
+            if (requiredCyclicModule._asyncEvaluation)
+            {
+                _pendingAsyncDependencies++;
+                requiredCyclicModule._asyncParentModules.Add(this);
+            }
+        }
+
+        Completion completion;
+
+        if (_pendingAsyncDependencies > 0 || _hasTLA)
+        {
+            if (_asyncEvaluation)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state (async evaluation is true)");
+            }
+
+            _asyncEvaluation = true;
+            _asyncEvalOrder = asyncEvalOrder++;
+            if (_pendingAsyncDependencies == 0)
+            {
+                completion = ExecuteAsyncModule();
+            }
+            else
+            {
+                // This is not in the specifications, but it's unclear whether 16.2.1.5.2.1.13 "Otherwise" should mean "Else" for 12 or "In other cases"..
+                completion = ExecuteModule();
+            }
+        }
+        else
+        {
+            completion = ExecuteModule();
+        }
+
+        if (StackReferenceCount(stack) != 1)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state (not found exactly once in stack)");
+        }
+
+        if (_dfsAncestorIndex > _dfsIndex)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state (mismatch DFS ancestor index)");
+        }
+
+        if (_dfsIndex == _dfsAncestorIndex)
+        {
+            var done = false;
+            while (!done)
+            {
+                var requiredModule = stack.Pop();
+                if (!requiredModule._asyncEvaluation)
+                {
+                    requiredModule.Status = ModuleStatus.Evaluated;
+                }
+                else
+                {
+                    requiredModule.Status = ModuleStatus.EvaluatingAsync;
+                }
+
+                done = requiredModule == this;
+                requiredModule._cycleRoot = this;
+            }
+        }
+
+        return completion;
+    }
+
+    private int StackReferenceCount(Stack<CyclicModuleRecord> stack)
+    {
+        var count = 0;
+        foreach (var item in stack)
+        {
+            if (ReferenceEquals(item, this))
+            {
+                count++;
+            }
+        }
+
+        return count;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-execute-async-module
+    /// </summary>
+    private Completion ExecuteAsyncModule()
+    {
+        if (Status != ModuleStatus.Evaluating && Status != ModuleStatus.EvaluatingAsync || !_hasTLA)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+        }
+
+        var capability = PromiseConstructor.NewPromiseCapability(_engine, _realm.Intrinsics.Promise);
+
+        var onFullfilled = new ClrFunctionInstance(_engine, "fulfilled", AsyncModuleExecutionFulfilled, 1, PropertyFlag.Configurable);
+        var onRejected = new ClrFunctionInstance(_engine, "rejected", AsyncModuleExecutionRejected, 1, PropertyFlag.Configurable);
+
+        PromiseOperations.PerformPromiseThen(_engine, (PromiseInstance) capability.PromiseInstance, onFullfilled, onRejected, null);
+
+        return ExecuteModule(capability);
+    }
+
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-async-module-execution-fulfilled
+    /// </summary>
+    private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] arguments)
+    {
+        var module = (CyclicModuleRecord) arguments.At(0);
+        if (module.Status == ModuleStatus.Evaluated)
+        {
+            if (module._evalError is not null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+
+            return Undefined;
+        }
+
+        if (module.Status != ModuleStatus.EvaluatingAsync ||
+            !module._asyncEvaluation ||
+            module._evalError is not null)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+        }
+
+        if (module._topLevelCapability is not null)
+        {
+            if (module._cycleRoot is null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+
+            module._topLevelCapability.Resolve.Call(Undefined, Array.Empty<JsValue>());
+        }
+
+        var execList = new List<CyclicModuleRecord>();
+        module.GatherAvailableAncestors(execList);
+        execList.Sort((x, y) => x._asyncEvalOrder - y._asyncEvalOrder);
+
+        for (var i = 0; i < execList.Count; i++)
+        {
+            var m = execList[i];
+            if (m.Status == ModuleStatus.Evaluated && m._evalError is null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+            else if (m._hasTLA)
+            {
+                m.ExecuteAsyncModule();
+            }
+            else
+            {
+                var result = m.ExecuteModule();
+                if (result.Type != CompletionType.Normal)
+                {
+                    AsyncModuleExecutionRejected(Undefined, new[] { m, result.Value });
+                }
+                else
+                {
+                    m.Status = ModuleStatus.Evaluated;
+                    if (m._topLevelCapability is not null)
+                    {
+                        if (m._cycleRoot is null)
+                        {
+                            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+                        }
+
+                        m._topLevelCapability.Resolve.Call(Undefined, Array.Empty<JsValue>());
+                    }
+                }
+            }
+        }
+
+        return Undefined;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-async-module-execution-rejected
+    /// </summary>
+    private static JsValue AsyncModuleExecutionRejected(JsValue thisObj, JsValue[] arguments)
+    {
+        var module = (SourceTextModuleRecord) arguments.At(0);
+        var error = arguments.At(1);
+
+        if (module.Status == ModuleStatus.Evaluated)
+        {
+            if (module._evalError is null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+
+            return Undefined;
+        }
+
+        if (module.Status != ModuleStatus.EvaluatingAsync ||
+            !module._asyncEvaluation ||
+            module._evalError is not null)
+        {
+            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+        }
+
+        module._evalError = new Completion(CompletionType.Throw, error, null, default);
+        module.Status = ModuleStatus.Evaluated;
+
+        var asyncParentModules = module._asyncParentModules;
+        for (var i = 0; i < asyncParentModules.Count; i++)
+        {
+            var m = asyncParentModules[i];
+            AsyncModuleExecutionRejected(thisObj, new[] { m, error });
+        }
+
+        if (module._topLevelCapability is not null)
+        {
+            if (module._cycleRoot is null)
+            {
+                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+            }
+
+            module._topLevelCapability.Reject.Call(Undefined, new[] { error });
+        }
+
+        return Undefined;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-gather-available-ancestors
+    /// </summary>
+    private void GatherAvailableAncestors(List<CyclicModuleRecord> execList)
+    {
+        foreach (var m in _asyncParentModules)
+        {
+            if (!execList.Contains(m) && m._cycleRoot._evalError is null)
+            {
+                if (m.Status != ModuleStatus.EvaluatingAsync ||
+                    m._evalError is not null ||
+                    !m._asyncEvaluation ||
+                    m._pendingAsyncDependencies <= 0)
+                {
+                    ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
+                }
+
+                if (--m._pendingAsyncDependencies == 0)
+                {
+                    execList.Add(m);
+                    if (!m._hasTLA)
+                    {
+                        m.GatherAvailableAncestors(execList);
+                    }
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#table-cyclic-module-methods
+    /// </summary>
+    protected abstract void InitializeEnvironment();
+
+    internal abstract Completion ExecuteModule(PromiseCapability capability = null);
+}

+ 5 - 0
Jint/Runtime/Modules/DefaultModuleLoader.cs

@@ -145,6 +145,11 @@ public sealed class DefaultModuleLoader : IModuleLoader
             ExceptionHelper.ThrowSyntaxError(engine.Realm, $"Error while loading module: error in module '{resolved.Uri.LocalPath}': {ex.Error}");
             module = null;
         }
+        catch (Exception)
+        {
+            ExceptionHelper.ThrowJavaScriptException(engine, $"Could not load module {resolved.Uri?.LocalPath}", Completion.Empty());
+            module = null;
+        }
 
         return module;
     }

+ 0 - 952
Jint/Runtime/Modules/JsModule.cs

@@ -1,952 +0,0 @@
-using System;
-using Esprima.Ast;
-using System.Collections.Generic;
-using System.Linq;
-using Esprima;
-using Jint.Native;
-using Jint.Native.Object;
-using Jint.Native.Promise;
-using Jint.Runtime.Descriptors;
-using Jint.Runtime.Environments;
-using Jint.Runtime.Interop;
-using Jint.Runtime.Interpreter;
-
-namespace Jint.Runtime.Modules;
-
-#pragma warning disable CS0649 // never assigned to, waiting for new functionalities in spec
-
-internal sealed record ResolvedBinding(JsModule Module, string BindingName)
-{
-    internal static ResolvedBinding Ambiguous => new(null, "ambiguous");
-}
-
-internal sealed record ImportEntry(
-    string ModuleRequest,
-    string ImportName,
-    string LocalName
-);
-
-internal sealed record ExportEntry(
-    string ExportName,
-    string ModuleRequest,
-    string ImportName,
-    string LocalName
-);
-
-internal sealed record ExportResolveSetItem(
-    JsModule Module,
-    string ExportName
-);
-
-/// <summary>
-/// Represents a module record
-/// https://tc39.es/ecma262/#sec-abstract-module-records
-/// https://tc39.es/ecma262/#sec-cyclic-module-records
-/// https://tc39.es/ecma262/#sec-source-text-module-records
-/// </summary>
-public sealed class JsModule : JsValue, IScriptOrModule
-{
-    private readonly Engine _engine;
-    private readonly Realm _realm;
-    internal ModuleEnvironmentRecord _environment;
-    private ObjectInstance _namespace;
-    private Completion? _evalError;
-    private int _dfsIndex;
-    private int _dfsAncestorIndex;
-    private readonly HashSet<string> _requestedModules;
-    private JsModule _cycleRoot;
-    private bool _hasTLA;
-    private bool _asyncEvaluation;
-    private PromiseCapability _topLevelCapability;
-    private List<JsModule> _asyncParentModules;
-    private int _asyncEvalOrder;
-    private int _pendingAsyncDependencies;
-
-    private readonly Module _source;
-    private ExecutionContext _context;
-    private readonly ObjectInstance _importMeta;
-    private readonly List<ImportEntry> _importEntries;
-    private readonly List<ExportEntry> _localExportEntries;
-    private readonly List<ExportEntry> _indirectExportEntries;
-    private readonly List<ExportEntry> _starExportEntries;
-    internal JsValue _evalResult;
-
-    internal JsModule(Engine engine, Realm realm, Module source, string location, bool async) : base(InternalTypes.Module)
-    {
-        _engine = engine;
-        _realm = realm;
-        _source = source;
-        Location = location;
-
-        _importMeta = _realm.Intrinsics.Object.Construct(1);
-        _importMeta.DefineOwnProperty("url", new PropertyDescriptor(location, PropertyFlag.ConfigurableEnumerableWritable));
-
-        HoistingScope.GetImportsAndExports(
-            _source,
-            out _requestedModules,
-            out _importEntries,
-            out _localExportEntries,
-            out _indirectExportEntries,
-            out _starExportEntries);
-
-        //ToDo async modules
-
-    }
-
-    public string Location { get; }
-    internal ModuleStatus Status { get; private set; }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-getmodulenamespace
-    /// </summary>
-    public static ObjectInstance GetModuleNamespace(JsModule module)
-    {
-        var ns = module._namespace;
-        if(ns is null)
-        {
-            var exportedNames = module.GetExportedNames();
-            var unambiguousNames = new List<string>();
-            for (var i = 0; i < exportedNames.Count; i++)
-            {
-                var name = exportedNames[i];
-                var resolution = module.ResolveExport(name);
-                if(resolution is not null)
-                {
-                    unambiguousNames.Add(name);
-                }
-            }
-
-            ns = CreateModuleNamespace(module, unambiguousNames);
-        }
-
-        return ns;
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-modulenamespacecreate
-    /// </summary>
-    private static ObjectInstance CreateModuleNamespace(JsModule module, List<string> unambiguousNames)
-    {
-        var m = new ModuleNamespace(module._engine, module, unambiguousNames);
-        module._namespace = m;
-        return m;
-    }
-
-    internal void BindExportedValue(string name, JsValue value)
-    {
-        _environment.CreateImmutableBindingAndInitialize(name, true, value);
-        _localExportEntries.Add(new ExportEntry(name, null, null, null));
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-getexportednames
-    /// </summary>
-    public List<string> GetExportedNames(List<JsModule> exportStarSet = null)
-    {
-        exportStarSet ??= new();
-        if (exportStarSet.Contains(this))
-        {
-            //Reached the starting point of an export * circularity
-            return new();
-        }
-
-        exportStarSet.Add(this);
-        var exportedNames = new List<string>();
-        for (var i = 0; i < _localExportEntries.Count; i++)
-        {
-            var e = _localExportEntries[i];
-            exportedNames.Add(e.ImportName ?? e.ExportName);
-        }
-
-        for (var i = 0; i < _indirectExportEntries.Count; i++)
-        {
-            var e = _indirectExportEntries[i];
-            exportedNames.Add(e.ImportName ?? e.ExportName);
-        }
-
-        for(var i = 0; i < _starExportEntries.Count; i++)
-        {
-            var e = _starExportEntries[i];
-            var requestedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest);
-            var starNames = requestedModule.GetExportedNames(exportStarSet);
-
-            for (var j = 0; j < starNames.Count; j++)
-            {
-                var n = starNames[j];
-                if (!"default".Equals(n) && !exportedNames.Contains(n))
-                {
-                    exportedNames.Add(n);
-                }
-            }
-        }
-
-        return exportedNames;
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-resolveexport
-    /// </summary>
-    internal ResolvedBinding ResolveExport(string exportName, List<ExportResolveSetItem> resolveSet = null)
-    {
-        resolveSet ??= new();
-
-        for(var i = 0; i < resolveSet.Count; i++)
-        {
-            var r = resolveSet[i];
-            if(this == r.Module && exportName == r.ExportName)
-            {
-                //circular import request
-                return null;
-            }
-        }
-
-        resolveSet.Add(new(this, exportName));
-        for(var i = 0; i < _localExportEntries.Count; i++)
-        {
-            var e = _localExportEntries[i];
-
-            if (exportName == (e.ImportName ?? e.ExportName))
-            {
-                return new ResolvedBinding(this, e.LocalName ?? e.ExportName);
-            }
-        }
-
-        for(var i = 0; i < _indirectExportEntries.Count; i++)
-        {
-            var e = _localExportEntries[i];
-            if (exportName.Equals(e.ImportName ?? e.ExportName))
-            {
-                var importedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest);
-                if(e.ImportName == "*")
-                {
-                    return new ResolvedBinding(importedModule, "*namespace*");
-                }
-                else
-                {
-                    return importedModule.ResolveExport(e.ImportName, resolveSet);
-                }
-            }
-        }
-
-        if ("default".Equals(exportName))
-        {
-            return null;
-        }
-
-        ResolvedBinding starResolution = null;
-
-        for(var i = 0; i < _starExportEntries.Count; i++)
-        {
-            var e = _starExportEntries[i];
-            var importedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest);
-            var resolution = importedModule.ResolveExport(exportName, resolveSet);
-            if(resolution == ResolvedBinding.Ambiguous)
-            {
-                return resolution;
-            }
-
-            if(resolution is not null)
-            {
-                if(starResolution is null)
-                {
-                    starResolution = resolution;
-                }
-                else
-                {
-                    if(resolution.Module != starResolution.Module || resolution.BindingName != starResolution.BindingName)
-                    {
-                        return ResolvedBinding.Ambiguous;
-                    }
-                }
-            }
-        }
-
-        return starResolution;
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-moduledeclarationlinking
-    /// </summary>
-    public void Link()
-    {
-        if (Status == ModuleStatus.Linking || Status == ModuleStatus.Evaluating)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module is already either linking or evaluating");
-        }
-
-        var stack = new Stack<JsModule>();
-
-        try
-        {
-            Link(this, stack, 0);
-        }
-        catch
-        {
-            foreach (var m in stack)
-            {
-                m.Status = ModuleStatus.Unlinked;
-                m._environment = null;
-                m._dfsIndex = -1;
-                m._dfsAncestorIndex = -1;
-            }
-            Status = ModuleStatus.Unlinked;
-            throw;
-        }
-
-        if (Status != ModuleStatus.Linked && Status != ModuleStatus.Unlinked)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module is neither linked or unlinked");
-        }
-
-        if(stack.Any())
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: One or more modules were not linked");
-        }
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-moduleevaluation
-    /// </summary>
-    public JsValue Evaluate()
-    {
-        var module = this;
-
-        if (module.Status != ModuleStatus.Linked &&
-            module.Status != ModuleStatus.EvaluatingAsync &&
-            module.Status != ModuleStatus.Evaluated)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-        if (module.Status == ModuleStatus.EvaluatingAsync || module.Status == ModuleStatus.Evaluated)
-        {
-            module = module._cycleRoot;
-        }
-
-        if (module._topLevelCapability is not null)
-        {
-            return module._topLevelCapability.PromiseInstance;
-        }
-
-        var stack = new Stack<JsModule>();
-        var capability = PromiseConstructor.NewPromiseCapability(_engine, _realm.Intrinsics.Promise);
-        int asyncEvalOrder = 0;
-        module._topLevelCapability = capability;
-
-        var result = Evaluate(module, stack, 0, ref asyncEvalOrder);
-
-        if(result.Type != CompletionType.Normal)
-        {
-            foreach(var m in stack)
-            {
-                m.Status = ModuleStatus.Evaluated;
-                m._evalError = result;
-            }
-            capability.Reject.Call(Undefined, new [] { result.Value });
-        }
-        else
-        {
-            if (module.Status != ModuleStatus.EvaluatingAsync && module.Status != ModuleStatus.Evaluated)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            if (module._evalError is not null)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            if (!module._asyncEvaluation)
-            {
-                if(module.Status != ModuleStatus.Evaluated)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-                }
-
-                capability.Resolve.Call(Undefined, Array.Empty<JsValue>());
-            }
-
-            if (stack.Any())
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-        }
-
-        return capability.PromiseInstance;
-
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-InnerModuleLinking
-    /// </summary>
-    private int Link(JsModule module, Stack<JsModule> stack, int index)
-    {
-        if(module.Status is
-           ModuleStatus.Linking or
-           ModuleStatus.Linked or
-           ModuleStatus.EvaluatingAsync or
-           ModuleStatus.Evaluating)
-        {
-            return index;
-        }
-
-        if(module.Status != ModuleStatus.Unlinked)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module in an invalid state");
-        }
-
-        module.Status = ModuleStatus.Linking;
-        module._dfsIndex = index;
-        module._dfsAncestorIndex = index;
-        index++;
-        stack.Push(module);
-
-        var requestedModules = module._requestedModules;
-
-        foreach (var moduleSpecifier in requestedModules)
-        {
-            var requiredModule = _engine._host.ResolveImportedModule(module, moduleSpecifier);
-
-            //TODO: Should we link only when a module is requested? https://tc39.es/ecma262/#sec-example-cyclic-module-record-graphs Should we support retry?
-            if (requiredModule.Status == ModuleStatus.Unlinked)
-                requiredModule.Link();
-
-            if (requiredModule.Status != ModuleStatus.Linking &&
-                requiredModule.Status != ModuleStatus.Linked &&
-                requiredModule.Status != ModuleStatus.Evaluated)
-            {
-                ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Required module is in an invalid state: {requiredModule.Status}");
-            }
-
-            if(requiredModule.Status == ModuleStatus.Linking && !stack.Contains(requiredModule))
-            {
-                ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Required module is in an invalid state: {requiredModule.Status}");
-            }
-
-            if (requiredModule.Status == ModuleStatus.Linking)
-            {
-                module._dfsAncestorIndex = System.Math.Min(module._dfsAncestorIndex, requiredModule._dfsAncestorIndex);
-            }
-        }
-
-        module.InitializeEnvironment();
-
-        if (stack.Count(m => m == module) != 1)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Recursive dependency detected");
-        }
-
-        if (module._dfsIndex > module._dfsAncestorIndex)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Recursive dependency detected");
-        }
-
-        if (module._dfsIndex == module._dfsAncestorIndex)
-        {
-            while (true)
-            {
-                var requiredModule = stack.Pop();
-                requiredModule.Status = ModuleStatus.Linked;
-                if (requiredModule == module)
-                {
-                    break;
-                }
-            }
-        }
-
-        return index;
-
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-innermoduleevaluation
-    /// </summary>
-    private Completion Evaluate(JsModule module, Stack<JsModule> stack, int index, ref int asyncEvalOrder)
-    {
-        if(module.Status == ModuleStatus.EvaluatingAsync || module.Status == ModuleStatus.Evaluated)
-        {
-            if(module._evalError is null)
-            {
-                return new Completion(CompletionType.Normal, index, null, default);
-            }
-
-            return module._evalError.Value;
-        }
-
-        if(module.Status == ModuleStatus.Evaluating)
-        {
-            return new Completion(CompletionType.Normal, index, null, default);
-        }
-
-        if (module.Status != ModuleStatus.Linked)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-
-        module.Status = ModuleStatus.Evaluating;
-        module._dfsIndex = index;
-        module._dfsAncestorIndex = index;
-        module._pendingAsyncDependencies = 0;
-        index++;
-        stack.Push(module);
-
-        var requestedModules = module._requestedModules;
-
-        foreach (var moduleSpecifier in requestedModules)
-        {
-            var requiredModule = _engine._host.ResolveImportedModule(module, moduleSpecifier);
-            var result = Evaluate(module, stack, index, ref asyncEvalOrder);
-            if(result.Type != CompletionType.Normal)
-            {
-                return result;
-            }
-
-            index = TypeConverter.ToInt32(result.Value);
-
-            // TODO: Validate this behavior: https://tc39.es/ecma262/#sec-example-cyclic-module-record-graphs
-            if (requiredModule.Status == ModuleStatus.Linked)
-            {
-                var evaluationResult = requiredModule.Evaluate();
-                if (evaluationResult == null)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise");
-                }
-                else if (evaluationResult is not PromiseInstance promise)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}");
-                }
-                else if (promise.State == PromiseState.Rejected)
-                {
-                    ExceptionHelper.ThrowJavaScriptException(_engine, promise.Value, new Completion(CompletionType.Throw, promise.Value, null, new Location(new Position(), new Position(), moduleSpecifier)));
-                }
-                else if (promise.State != PromiseState.Fulfilled)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a fulfilled promise: {promise.State}");
-                }
-            }
-
-            if (requiredModule.Status != ModuleStatus.Evaluating &&
-                requiredModule.Status != ModuleStatus.EvaluatingAsync &&
-                requiredModule.Status != ModuleStatus.Evaluated)
-            {
-                ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module is in an invalid state: {requiredModule.Status}");
-            }
-
-            if (requiredModule.Status == ModuleStatus.Evaluating && !stack.Contains(requiredModule))
-            {
-                ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module is in an invalid state: {requiredModule.Status}");
-            }
-
-            if(requiredModule.Status == ModuleStatus.Evaluating)
-            {
-                module._dfsAncestorIndex = System.Math.Min(module._dfsAncestorIndex, requiredModule._dfsAncestorIndex);
-            }
-            else
-            {
-                requiredModule = requiredModule._cycleRoot;
-                if(requiredModule.Status != ModuleStatus.EvaluatingAsync && requiredModule.Status != ModuleStatus.Evaluated)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-                }
-            }
-
-            if (requiredModule._asyncEvaluation)
-            {
-                module._pendingAsyncDependencies++;
-                requiredModule._asyncParentModules.Add(module);
-            }
-        }
-
-        Completion completion;
-
-        if(module._pendingAsyncDependencies > 0 || module._hasTLA)
-        {
-            if (module._asyncEvaluation)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            module._asyncEvaluation = true;
-            module._asyncEvalOrder = asyncEvalOrder++;
-            if (module._pendingAsyncDependencies == 0)
-            {
-                completion = module.ExecuteAsync();
-            }
-            else
-            {
-                completion = module.Execute();
-            }
-        }
-        else
-        {
-            completion = module.Execute();
-        }
-
-        if(stack.Count(x => x == module) != 1)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-        if (module._dfsAncestorIndex > module._dfsIndex)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-        if(module._dfsIndex == module._dfsAncestorIndex)
-        {
-            bool done = false;
-            while (!done)
-            {
-                var requiredModule = stack.Pop();
-                if (!requiredModule._asyncEvaluation)
-                {
-                    requiredModule.Status = ModuleStatus.Evaluated;
-                }
-                else
-                {
-                    requiredModule.Status = ModuleStatus.EvaluatingAsync;
-                }
-
-                done = requiredModule == module;
-                requiredModule._cycleRoot = module;
-            }
-        }
-
-        return completion;
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment
-    /// </summary>
-    private void InitializeEnvironment()
-    {
-        for(var i = 0; i < _indirectExportEntries.Count; i++)
-        {
-            var e = _indirectExportEntries[i];
-            var resolution = ResolveExport(e.ExportName);
-            if (resolution is null || resolution == ResolvedBinding.Ambiguous)
-            {
-                ExceptionHelper.ThrowSyntaxError(_realm, "Ambiguous import statement for identifier: " + e.ExportName);
-            }
-        }
-
-        var realm = _realm;
-        var env = JintEnvironment.NewModuleEnvironment(_engine, realm.GlobalEnv);
-        _environment = env;
-
-        if (_importEntries != null)
-        {
-            for (var i = 0; i < _importEntries.Count; i++)
-            {
-                var ie = _importEntries[i];
-                var importedModule = _engine._host.ResolveImportedModule(this, ie.ModuleRequest);
-                if (ie.ImportName == "*")
-                {
-                    var ns = GetModuleNamespace(importedModule);
-                    env.CreateImmutableBinding(ie.LocalName, true);
-                    env.InitializeBinding(ie.LocalName, ns);
-                }
-                else
-                {
-                    var resolution = importedModule.ResolveExport(ie.ImportName);
-                    if (resolution is null || resolution == ResolvedBinding.Ambiguous)
-                    {
-                        ExceptionHelper.ThrowSyntaxError(_realm, "Ambigous import statement for identifier " + ie.ImportName);
-                    }
-
-                    if (resolution.BindingName == "*namespace*")
-                    {
-                        var ns = GetModuleNamespace(resolution.Module);
-                        env.CreateImmutableBinding(ie.LocalName, true);
-                        env.InitializeBinding(ie.LocalName, ns);
-                    }
-                    else
-                    {
-                        env.CreateImportBinding(ie.LocalName, resolution.Module, resolution.BindingName);
-                    }
-                }
-            }
-        }
-
-        var moduleContext = new ExecutionContext(this, _environment, _environment, null, realm, null);
-        _context = moduleContext;
-
-        _engine.EnterExecutionContext(_context);
-
-        var hoistingScope = HoistingScope.GetModuleLevelDeclarations(_source);
-
-        var varDeclarations = hoistingScope._variablesDeclarations;
-        var declaredVarNames = new List<string>();
-        if(varDeclarations != null)
-        {
-            var boundNames = new List<string>();
-            for(var i = 0; i < varDeclarations.Count; i++)
-            {
-                var d = varDeclarations[i];
-                boundNames.Clear();
-                d.GetBoundNames(boundNames);
-                for(var j = 0; j < boundNames.Count; j++)
-                {
-                    var dn = boundNames[j];
-                    if (!declaredVarNames.Contains(dn))
-                    {
-                        env.CreateMutableBinding(dn, false);
-                        env.InitializeBinding(dn, Undefined);
-                        declaredVarNames.Add(dn);
-                    }
-                }
-            }
-        }
-
-        var lexDeclarations = hoistingScope._lexicalDeclarations;
-
-        if(lexDeclarations != null)
-        {
-            var boundNames = new List<string>();
-            for(var i = 0; i < lexDeclarations.Count; i++)
-            {
-                var d = lexDeclarations[i];
-                boundNames.Clear();
-                d.GetBoundNames(boundNames);
-                for (var j = 0; j < boundNames.Count; j++)
-                {
-                    var dn = boundNames[j];
-                    if(d.IsConstantDeclaration())
-                    {
-                        env.CreateImmutableBinding(dn, true);
-                    }
-                    else
-                    {
-                        env.CreateMutableBinding(dn, false);
-                    }
-                }
-            }
-        }
-
-        var functionDeclarations = hoistingScope._functionDeclarations;
-
-        if(functionDeclarations != null)
-        {
-            for(var i = 0; i < functionDeclarations.Count; i++)
-            {
-                var d = functionDeclarations[i];
-                var fn = d.Id.Name;
-                var fd = new JintFunctionDefinition(_engine, d);
-                env.CreateImmutableBinding(fn, true);
-                var fo = realm.Intrinsics.Function.InstantiateFunctionObject(fd, env);
-                env.InitializeBinding(fn, fo);
-            }
-        }
-
-        _engine.LeaveExecutionContext();
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-source-text-module-record-execute-module
-    /// </summary>
-    private Completion Execute(PromiseCapability capability = null)
-    {
-        var moduleContext = new ExecutionContext(this, _environment, _environment, null, _realm);
-        if (!_hasTLA)
-        {
-            using (new StrictModeScope(strict: true))
-            {
-                _engine.EnterExecutionContext(moduleContext);
-                var statementList = new JintStatementList(null, _source.Body);
-                var result = statementList.Execute(_engine._activeEvaluationContext ?? new EvaluationContext(_engine)); //Create new evaluation context when called from e.g. module tests
-                _engine.LeaveExecutionContext();
-                return result;
-            }
-        }
-        else
-        {
-            //ToDo async modules
-            return default;
-        }
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-execute-async-module
-    /// </summary>
-    private Completion ExecuteAsync()
-    {
-        if((Status != ModuleStatus.Evaluating && Status != ModuleStatus.EvaluatingAsync) || !_hasTLA)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-        var capability = PromiseConstructor.NewPromiseCapability(_engine, _realm.Intrinsics.Promise);
-
-        var onFullfilled = new ClrFunctionInstance(_engine, "fulfilled", AsyncModuleExecutionFulfilled, 1, PropertyFlag.Configurable);
-        var onRejected = new ClrFunctionInstance(_engine, "rejected", AsyncModuleExecutionRejected, 1, PropertyFlag.Configurable);
-
-        PromiseOperations.PerformPromiseThen(_engine, (PromiseInstance)capability.PromiseInstance, onFullfilled, onRejected, null);
-
-        return Execute(capability);
-
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-gather-available-ancestors
-    /// </summary>
-    private void GatherAvailableAncestors(List<JsModule> execList)
-    {
-        foreach(var m in _asyncParentModules)
-        {
-            if(!execList.Contains(m) && m._cycleRoot._evalError is null)
-            {
-                if(m.Status != ModuleStatus.EvaluatingAsync ||
-                   m._evalError is not null ||
-                   !m._asyncEvaluation ||
-                   m._pendingAsyncDependencies <= 0)
-                {
-                    ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-                }
-
-                if(--m._pendingAsyncDependencies == 0)
-                {
-                    execList.Add(m);
-                    if (!m._hasTLA)
-                    {
-                        m.GatherAvailableAncestors(execList);
-                    }
-                }
-            }
-        }
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-async-module-execution-fulfilled
-    /// </summary>
-    private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] arguments)
-    {
-        var module = (JsModule)arguments.At(0);
-        if (module.Status == ModuleStatus.Evaluated)
-        {
-            if(module._evalError is not null)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            return Undefined;
-        }
-
-        if (module.Status != ModuleStatus.EvaluatingAsync ||
-            !module._asyncEvaluation ||
-            module._evalError is not null)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-        if (module._topLevelCapability is not null)
-        {
-            if(module._cycleRoot is null)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            module._topLevelCapability.Resolve.Call(Undefined, Array.Empty<JsValue>());
-        }
-
-        var execList = new List<JsModule>();
-        module.GatherAvailableAncestors(execList);
-        execList.Sort((x, y) => x._asyncEvalOrder - y._asyncEvalOrder);
-
-        for(var i = 0; i < execList.Count; i++)
-        {
-            var m = execList[i];
-            if (m.Status == ModuleStatus.Evaluated && m._evalError is null)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-            else if (m._hasTLA)
-            {
-                m.ExecuteAsync();
-            }
-            else
-            {
-                var result = m.Execute();
-                if(result.Type != CompletionType.Normal)
-                {
-                    AsyncModuleExecutionRejected(Undefined, new[] { m, result.Value });
-                }
-                else
-                {
-                    m.Status = ModuleStatus.Evaluated;
-                    if(m._topLevelCapability is not null)
-                    {
-                        if (m._cycleRoot is null)
-                        {
-                            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-                        }
-
-                        m._topLevelCapability.Resolve.Call(Undefined, Array.Empty<JsValue>());
-                    }
-                }
-            }
-        }
-
-        return Undefined;
-    }
-
-    /// <summary>
-    /// https://tc39.es/ecma262/#sec-async-module-execution-rejected
-    /// </summary>
-    private JsValue AsyncModuleExecutionRejected(JsValue thisObj, JsValue[] arguments)
-    {
-        JsModule module = (JsModule)arguments.At(0);
-        JsValue error = arguments.At(1);
-
-        if (module.Status == ModuleStatus.Evaluated)
-        {
-            if(module._evalError is null)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            return Undefined;
-        }
-
-        if (module.Status != ModuleStatus.EvaluatingAsync ||
-            !module._asyncEvaluation ||
-            module._evalError is not null)
-        {
-            ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-        }
-
-        module._evalError = new Completion(CompletionType.Throw, error, null, default);
-        module.Status = ModuleStatus.Evaluated;
-
-        var asyncParentModules = module._asyncParentModules;
-        for (var i = 0; i < asyncParentModules.Count; i++)
-        {
-            var m = asyncParentModules[i];
-            AsyncModuleExecutionRejected(thisObj, new[] { m, error });
-        }
-
-        if (module._topLevelCapability is not null)
-        {
-            if (module._cycleRoot is null)
-            {
-                ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state");
-            }
-
-            module._topLevelCapability.Reject.Call(Undefined, new [] { error });
-        }
-
-
-        return Undefined;
-    }
-
-    public override object ToObject()
-    {
-        ExceptionHelper.ThrowNotSupportedException();
-        return null;
-    }
-
-    public override string ToString()
-    {
-        return $"{Type}: {Location}";
-    }
-}

+ 74 - 9
Jint/Runtime/Modules/ModuleNamespace.cs

@@ -1,6 +1,9 @@
 using System.Collections.Generic;
+using Jint.Collections;
 using Jint.Native;
+using Jint.Native.Array;
 using Jint.Native.Object;
+using Jint.Native.Symbol;
 using Jint.Runtime.Descriptors;
 
 namespace Jint.Runtime.Modules;
@@ -10,30 +13,56 @@ namespace Jint.Runtime.Modules;
 /// </summary>
 internal sealed class ModuleNamespace : ObjectInstance
 {
-    private readonly JsModule _module;
+    private readonly ModuleRecord _module;
     private readonly HashSet<string> _exports;
 
-    public ModuleNamespace(Engine engine, JsModule module, List<string> exports) : base(engine)
+    public ModuleNamespace(Engine engine, ModuleRecord module, List<string> exports) : base(engine)
     {
         _module = module;
-        exports.Sort();
         _exports = new HashSet<string>(exports);
     }
 
+    protected override void Initialize()
+    {
+        var symbols = new SymbolDictionary(1)
+        {
+            [GlobalSymbolRegistry.ToStringTag] = new("Module", false, false, false)
+        };
+        SetSymbols(symbols);
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-getprototypeof
+    /// </summary>
     protected internal override ObjectInstance GetPrototypeOf() => null;
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-setprototypeof-v
+    /// </summary>
     public override bool SetPrototypeOf(JsValue value) => SetImmutablePrototype(value);
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-set-immutable-prototype
+    /// </summary>
     private bool SetImmutablePrototype(JsValue value)
     {
         var current = GetPrototypeOf();
         return SameValue(value, current ?? Null);
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-isextensible
+    /// </summary>
     public override bool Extensible => false;
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-preventextensions
+    /// </summary>
     public override bool PreventExtensions() => true;
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-getownproperty-p
+    /// </summary>
     public override PropertyDescriptor GetOwnProperty(JsValue property)
     {
         if (property.IsSymbol())
@@ -52,6 +81,9 @@ internal sealed class ModuleNamespace : ObjectInstance
         return new PropertyDescriptor(value, true, true, false);
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-defineownproperty-p-desc
+    /// </summary>
     public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc)
     {
         if (property.IsSymbol())
@@ -66,7 +98,22 @@ internal sealed class ModuleNamespace : ObjectInstance
             return false;
         }
 
-        if (desc.Configurable || desc.Enumerable || desc.IsAccessorDescriptor() || !desc.Writable)
+        if (desc.Configurable)
+        {
+            return false;
+        }
+
+        if (desc.EnumerableSet && !desc.Enumerable)
+        {
+            return false;
+        }
+
+        if (desc.IsAccessorDescriptor())
+        {
+            return false;
+        }
+
+        if (desc.WritableSet && !desc.Writable)
         {
             return false;
         }
@@ -79,6 +126,9 @@ internal sealed class ModuleNamespace : ObjectInstance
         return true;
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-hasproperty-p
+    /// </summary>
     public override bool HasProperty(JsValue property)
     {
         if (property.IsSymbol())
@@ -90,7 +140,9 @@ internal sealed class ModuleNamespace : ObjectInstance
         return _exports.Contains(p);
     }
 
-    // https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver
+    /// </summary>
     public override JsValue Get(JsValue property, JsValue receiver)
     {
         if (property.IsSymbol())
@@ -111,7 +163,7 @@ internal sealed class ModuleNamespace : ObjectInstance
 
         if (binding.BindingName == "*namespace*")
         {
-            return JsModule.GetModuleNamespace(targetModule);
+            return ModuleRecord.GetModuleNamespace(targetModule);
         }
 
         var targetEnv = targetModule._environment;
@@ -123,11 +175,17 @@ internal sealed class ModuleNamespace : ObjectInstance
         return targetEnv.GetBindingValue(binding.BindingName, true);
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-set-p-v-receiver
+    /// </summary>
     public override bool Set(JsValue property, JsValue value, JsValue receiver)
     {
         return false;
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-delete-p
+    /// </summary>
     public override bool Delete(JsValue property)
     {
         if (property.IsSymbol())
@@ -139,17 +197,24 @@ internal sealed class ModuleNamespace : ObjectInstance
         return !_exports.Contains(p);
     }
 
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-ownpropertykeys
+    /// </summary>
     public override List<JsValue> GetOwnPropertyKeys(Types types = Types.String | Types.Symbol)
     {
-        var keys = base.GetOwnPropertyKeys(types);
+        var result = new List<JsValue>();
         if ((types & Types.String) != 0)
         {
+            result.Capacity = _exports.Count;
             foreach (var export in _exports)
             {
-                keys.Add(export);
+                result.Add(export);
             }
+            result.Sort(ArrayPrototype.ArrayComparer.Default);
         }
+        
+        result.AddRange(base.GetOwnPropertyKeys(types));
 
-        return keys;
+        return result;
     }
 }

+ 86 - 0
Jint/Runtime/Modules/ModuleRecord.cs

@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using Jint.Native;
+using Jint.Native.Object;
+using Jint.Runtime.Environments;
+
+namespace Jint.Runtime.Modules;
+
+internal sealed record ExportResolveSetItem(
+    CyclicModuleRecord Module,
+    string ExportName
+);
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-abstract-module-records
+/// </summary>
+public abstract class ModuleRecord : JsValue, IScriptOrModule
+{
+    private ObjectInstance _namespace;
+    protected readonly Engine _engine;
+    protected readonly Realm _realm;
+    internal ModuleEnvironmentRecord _environment;
+
+    public string Location { get; }
+
+    internal ModuleRecord(Engine engine, Realm realm, string location) : base(InternalTypes.Module)
+    {
+        _engine = engine;
+        _realm = realm;
+        Location = location;
+    }
+
+    public abstract List<string> GetExportedNames(List<CyclicModuleRecord> exportStarSet = null);
+    internal abstract ResolvedBinding ResolveExport(string exportName, List<ExportResolveSetItem> resolveSet = null);
+    public abstract void Link();
+    public abstract JsValue Evaluate();
+
+    protected internal abstract int InnerModuleLinking(Stack<CyclicModuleRecord> stack, int index);
+    protected internal abstract Completion InnerModuleEvaluation(Stack<CyclicModuleRecord> stack, int index, ref int asyncEvalOrder);
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-getmodulenamespace
+    /// </summary>
+    public static ObjectInstance GetModuleNamespace(ModuleRecord module)
+    {
+        var ns = module._namespace;
+        if (ns is null)
+        {
+            var exportedNames = module.GetExportedNames();
+            var unambiguousNames = new List<string>();
+            for (var i = 0; i < exportedNames.Count; i++)
+            {
+                var name = exportedNames[i];
+                var resolution = module.ResolveExport(name);
+                if (resolution is not null && resolution != ResolvedBinding.Ambiguous)
+                {
+                    unambiguousNames.Add(name);
+                }
+            }
+
+            ns = CreateModuleNamespace(module, unambiguousNames);
+        }
+
+        return ns;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-modulenamespacecreate
+    /// </summary>
+    private static ObjectInstance CreateModuleNamespace(ModuleRecord module, List<string> unambiguousNames)
+    {
+        var m = new ModuleNamespace(module._engine, module, unambiguousNames);
+        module._namespace = m;
+        return m;
+    }
+
+    public override object ToObject()
+    {
+        ExceptionHelper.ThrowNotSupportedException();
+        return null;
+    }
+
+    public override string ToString()
+    {
+        return $"{Type}: {Location}";
+    }
+}

+ 341 - 0
Jint/Runtime/Modules/SourceTextModuleRecord.cs

@@ -0,0 +1,341 @@
+using System.Collections.Generic;
+using Esprima.Ast;
+using Jint.Native.Object;
+using Jint.Native.Promise;
+using Jint.Runtime.Descriptors;
+using Jint.Runtime.Environments;
+using Jint.Runtime.Interpreter;
+
+namespace Jint.Runtime.Modules;
+
+/// <summary>
+/// https://tc39.es/ecma262/#importentry-record
+/// </summary>
+internal sealed record ImportEntry(
+    string ModuleRequest,
+    string ImportName,
+    string LocalName
+);
+
+/// <summary>
+/// https://tc39.es/ecma262/#exportentry-record
+/// </summary>
+internal sealed record ExportEntry(
+    string ExportName,
+    string ModuleRequest,
+    string ImportName,
+    string LocalName
+);
+
+/// <summary>
+/// https://tc39.es/ecma262/#sec-source-text-module-records
+/// </summary>
+internal class SourceTextModuleRecord : CyclicModuleRecord
+{
+    private readonly Module _source;
+    private ExecutionContext _context;
+    private readonly ObjectInstance _importMeta;
+    private readonly List<ImportEntry> _importEntries;
+    internal readonly List<ExportEntry> _localExportEntries;
+    private readonly List<ExportEntry> _indirectExportEntries;
+    private readonly List<ExportEntry> _starExportEntries;
+
+    internal SourceTextModuleRecord(Engine engine, Realm realm, Module source, string location, bool async)
+        : base(engine, realm, source, location, async)
+    {
+        _source = source;
+
+        // https://tc39.es/ecma262/#sec-parsemodule
+        _importMeta = _realm.Intrinsics.Object.Construct(1);
+        _importMeta.DefineOwnProperty("url", new PropertyDescriptor(location, PropertyFlag.ConfigurableEnumerableWritable));
+
+        HoistingScope.GetImportsAndExports(
+            _source,
+            out _requestedModules,
+            out _importEntries,
+            out _localExportEntries,
+            out _indirectExportEntries,
+            out _starExportEntries);
+
+        //ToDo async modules
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-getexportednames
+    /// </summary>
+    public override List<string> GetExportedNames(List<CyclicModuleRecord> exportStarSet = null)
+    {
+        exportStarSet ??= new List<CyclicModuleRecord>();
+        if (exportStarSet.Contains(this))
+        {
+            //Reached the starting point of an export * circularity
+            return new List<string>();
+        }
+
+        exportStarSet.Add(this);
+        var exportedNames = new List<string>();
+        for (var i = 0; i < _localExportEntries.Count; i++)
+        {
+            var e = _localExportEntries[i];
+            exportedNames.Add(e.ExportName);
+        }
+
+        for (var i = 0; i < _indirectExportEntries.Count; i++)
+        {
+            var e = _indirectExportEntries[i];
+            exportedNames.Add(e.ExportName);
+        }
+
+        for (var i = 0; i < _starExportEntries.Count; i++)
+        {
+            var e = _starExportEntries[i];
+            var requestedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest);
+            var starNames = requestedModule.GetExportedNames(exportStarSet);
+
+            for (var j = 0; j < starNames.Count; j++)
+            {
+                var n = starNames[j];
+                if (!"default".Equals(n) && !exportedNames.Contains(n))
+                {
+                    exportedNames.Add(n);
+                }
+            }
+        }
+
+        return exportedNames;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-resolveexport
+    /// </summary>
+    internal override ResolvedBinding ResolveExport(string exportName, List<ExportResolveSetItem> resolveSet = null)
+    {
+        resolveSet ??= new List<ExportResolveSetItem>();
+
+        for (var i = 0; i < resolveSet.Count; i++)
+        {
+            var r = resolveSet[i];
+            if (ReferenceEquals(this, r.Module) && exportName == r.ExportName)
+            {
+                // circular import request
+                return null;
+            }
+        }
+
+        resolveSet.Add(new ExportResolveSetItem(this, exportName));
+        for (var i = 0; i < _localExportEntries.Count; i++)
+        {
+            var e = _localExportEntries[i];
+            if (exportName == e.ExportName)
+            {
+                // i. Assert: module provides the direct binding for this export.
+                return new ResolvedBinding(this, e.LocalName);
+            }
+        }
+
+        for (var i = 0; i < _indirectExportEntries.Count; i++)
+        {
+            var e = _indirectExportEntries[i];
+            if (exportName == e.ExportName)
+            {
+                var importedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest);
+                if (e.ImportName == "*")
+                {
+                    // 1. Assert: module does not provide the direct binding for this export.
+                    return new ResolvedBinding(importedModule, "*namespace*");
+                }
+                else
+                {
+                    // 1. Assert: module imports a specific binding for this export.
+                    return importedModule.ResolveExport(e.ImportName, resolveSet);
+                }
+            }
+        }
+
+        if ("default".Equals(exportName))
+        {
+            // Assert: A default export was not explicitly defined by this module
+            return null;
+        }
+
+        ResolvedBinding starResolution = null;
+
+        for (var i = 0; i < _starExportEntries.Count; i++)
+        {
+            var e = _starExportEntries[i];
+            var importedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest);
+            var resolution = importedModule.ResolveExport(exportName, resolveSet);
+            if (resolution == ResolvedBinding.Ambiguous)
+            {
+                return resolution;
+            }
+
+            if (resolution is not null)
+            {
+                if (starResolution is null)
+                {
+                    starResolution = resolution;
+                }
+                else
+                {
+                    if (resolution.Module != starResolution.Module || resolution.BindingName != starResolution.BindingName)
+                    {
+                        return ResolvedBinding.Ambiguous;
+                    }
+                }
+            }
+        }
+
+        return starResolution;
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment
+    /// </summary>
+    protected override void InitializeEnvironment()
+    {
+        for (var i = 0; i < _indirectExportEntries.Count; i++)
+        {
+            var e = _indirectExportEntries[i];
+            var resolution = ResolveExport(e.ExportName);
+            if (resolution is null || resolution == ResolvedBinding.Ambiguous)
+            {
+                ExceptionHelper.ThrowSyntaxError(_realm, "Ambiguous import statement for identifier: " + e.ExportName);
+            }
+        }
+
+        var realm = _realm;
+        var env = JintEnvironment.NewModuleEnvironment(_engine, realm.GlobalEnv);
+        _environment = env;
+
+        if (_importEntries != null)
+        {
+            for (var i = 0; i < _importEntries.Count; i++)
+            {
+                var ie = _importEntries[i];
+                var importedModule = _engine._host.ResolveImportedModule(this, ie.ModuleRequest);
+                if (ie.ImportName == "*")
+                {
+                    var ns = GetModuleNamespace(importedModule);
+                    env.CreateImmutableBinding(ie.LocalName, true);
+                    env.InitializeBinding(ie.LocalName, ns);
+                }
+                else
+                {
+                    var resolution = importedModule.ResolveExport(ie.ImportName);
+                    if (resolution is null || resolution == ResolvedBinding.Ambiguous)
+                    {
+                        ExceptionHelper.ThrowSyntaxError(_realm, "Ambiguous import statement for identifier " + ie.ImportName);
+                    }
+
+                    if (resolution.BindingName == "*namespace*")
+                    {
+                        var ns = GetModuleNamespace(resolution.Module);
+                        env.CreateImmutableBinding(ie.LocalName, true);
+                        env.InitializeBinding(ie.LocalName, ns);
+                    }
+                    else
+                    {
+                        env.CreateImportBinding(ie.LocalName, resolution.Module, resolution.BindingName);
+                    }
+                }
+            }
+        }
+
+        var moduleContext = new ExecutionContext(this, _environment, _environment, null, realm, null);
+        _context = moduleContext;
+
+        _engine.EnterExecutionContext(_context);
+
+        var hoistingScope = HoistingScope.GetModuleLevelDeclarations(_source);
+
+        var varDeclarations = hoistingScope._variablesDeclarations;
+        var declaredVarNames = new HashSet<string>();
+        if (varDeclarations != null)
+        {
+            var boundNames = new List<string>();
+            for (var i = 0; i < varDeclarations.Count; i++)
+            {
+                var d = varDeclarations[i];
+                boundNames.Clear();
+                d.GetBoundNames(boundNames);
+                for (var j = 0; j < boundNames.Count; j++)
+                {
+                    var dn = boundNames[j];
+                    if (declaredVarNames.Add(dn))
+                    {
+                        env.CreateMutableBinding(dn, false);
+                        env.InitializeBinding(dn, Undefined);
+                    }
+                }
+            }
+        }
+
+        var lexDeclarations = hoistingScope._lexicalDeclarations;
+
+        if (lexDeclarations != null)
+        {
+            var boundNames = new List<string>();
+            for (var i = 0; i < lexDeclarations.Count; i++)
+            {
+                var d = lexDeclarations[i];
+                boundNames.Clear();
+                d.GetBoundNames(boundNames);
+                for (var j = 0; j < boundNames.Count; j++)
+                {
+                    var dn = boundNames[j];
+                    if (d.IsConstantDeclaration())
+                    {
+                        env.CreateImmutableBinding(dn, true);
+                    }
+                    else
+                    {
+                        env.CreateMutableBinding(dn, false);
+                    }
+                }
+            }
+        }
+
+        var functionDeclarations = hoistingScope._functionDeclarations;
+
+        if (functionDeclarations != null)
+        {
+            for (var i = 0; i < functionDeclarations.Count; i++)
+            {
+                var d = functionDeclarations[i];
+                var fn = d.Id?.Name ?? "*default*";
+                var fd = new JintFunctionDefinition(_engine, d);
+                env.CreateMutableBinding(fn, true);
+                var fo = realm.Intrinsics.Function.InstantiateFunctionObject(fd, env);
+                if (fn == "*default*") fo.SetFunctionName("default");
+                env.InitializeBinding(fn, fo);
+            }
+        }
+
+        _engine.LeaveExecutionContext();
+    }
+
+    /// <summary>
+    /// https://tc39.es/ecma262/#sec-source-text-module-record-execute-module
+    /// </summary>
+    internal override Completion ExecuteModule(PromiseCapability capability = null)
+    {
+        var moduleContext = new ExecutionContext(this, _environment, _environment, null, _realm);
+        if (!_hasTLA)
+        {
+            using (new StrictModeScope(true, force: true))
+            {
+                _engine.EnterExecutionContext(moduleContext);
+                var statementList = new JintStatementList(null, _source.Body);
+                var result = statementList.Execute(_engine._activeEvaluationContext ?? new EvaluationContext(_engine)); //Create new evaluation context when called from e.g. module tests
+                _engine.LeaveExecutionContext();
+                return result;
+            }
+        }
+        else
+        {
+            ExceptionHelper.ThrowNotImplementedException("async modules not implemented");
+            return default;
+        }
+    }
+}