Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Jint.Tests.Test262/Language/ModuleTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,10 @@ protected void Import(SourceFile sourceFile)
{
RunTestInternal(sourceFile);
}

private void RunModuleTest(SourceFile sourceFile)
{

}
}
}
141 changes: 141 additions & 0 deletions Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using Esprima;
using Esprima.Ast;
using System.Collections.Generic;
using Jint.Native;
using Jint.Native.Promise;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
using Jint.Runtime.Modules;

namespace Jint
{
public partial class Engine
{
public IModuleLoader ModuleLoader { get; internal set; }

private readonly Dictionary<ModuleCacheKey, JsModule> _modules = new Dictionary<ModuleCacheKey, JsModule>();

public JsModule LoadModule(string specifier) => LoadModule(null, specifier);

internal JsModule LoadModule(string referencingModuleLocation, string specifier)
{
var key = new ModuleCacheKey(referencingModuleLocation ?? string.Empty, specifier);

if(_modules.TryGetValue(key, out var module))
{
return module;
}

if(!ModuleLoader.TryLoadModule(specifier, referencingModuleLocation, out var moduleSourceCode, out var moduleLocation))
{
ExceptionHelper.ThrowSyntaxError(Realm, "Error while loading module: module with specifier '" + specifier + "' could not be located");
}

Module moduleSource;
try
{
var parserOptions = new ParserOptions(moduleLocation)
{
AdaptRegexp = true,
Tolerant = true
};

moduleSource = new JavaScriptParser(moduleSourceCode, parserOptions).ParseModule();
}
catch (ParserException ex)
{
ExceptionHelper.ThrowSyntaxError(Realm, "Error while loading module: error in module '" + specifier + "': " + ex.Error?.ToString() ?? ex.Message);
moduleSource = null;
}

module = new JsModule(this, _host.CreateRealm(), moduleSource, moduleLocation, false);

_modules[key] = module;

return module;

}

//https://tc39.es/ecma262/#sec-hostresolveimportedmodule
internal JsModule ResolveImportedModule(JsModule referencingModule, string specifier)
{
return LoadModule(referencingModule._location, specifier);
}

//https://tc39.es/ecma262/#sec-hostimportmoduledynamically
internal void ImportModuleDynamically(JsModule referencingModule, string specifier, PromiseCapability promiseCapability)
{

var promise = RegisterPromise();

try
{
LoadModule(referencingModule._location, specifier);
promise.Resolve(JsValue.Undefined);

}
catch (JavaScriptException ex)
{
promise.Reject(ex.Error);
}

FinishDynamicImport(referencingModule, specifier, promiseCapability, (PromiseInstance)promise.Promise);
}

//https://tc39.es/ecma262/#sec-finishdynamicimport
internal void FinishDynamicImport(JsModule referencingModule, string specifier, PromiseCapability promiseCapability, PromiseInstance innerPromise)
{
var onFulfilled = new ClrFunctionInstance(this, "", (thisObj, args) =>
{
var moduleRecord = ResolveImportedModule(referencingModule, specifier);
try
{
var ns = JsModule.GetModuleNamespace(moduleRecord);
promiseCapability.Resolve.Call(ns);
}
catch (JavaScriptException ex)
{
promiseCapability.Reject.Call(ex.Error);
}
return JsValue.Undefined;
}, 0, PropertyFlag.Configurable);

var onRejected = new ClrFunctionInstance(this, "", (thisObj, args) =>
{
var error = args.At(0);
promiseCapability.Reject.Call(error);
return JsValue.Undefined;
}, 0, PropertyFlag.Configurable);

PromiseOperations.PerformPromiseThen(this, innerPromise, onFulfilled, onRejected, null);
}

internal readonly struct ModuleCacheKey
{
internal readonly Key ReferencingModuleLocation;
internal readonly Key Specifier;
private readonly int _hashCode;

internal ModuleCacheKey(string referencingModuleLocation, string specifier)
{
ReferencingModuleLocation = referencingModuleLocation;
Specifier = specifier;
unchecked
{
_hashCode = 31 * ReferencingModuleLocation.HashCode + Specifier.HashCode;
}
}

public override bool Equals(object obj)
{
return (obj is ModuleCacheKey other) && (ReferencingModuleLocation.Equals(other.ReferencingModuleLocation) && Specifier.Equals(other.Specifier));
}

public override int GetHashCode()
{
return _hashCode;
}
}
}
}
1 change: 1 addition & 0 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using Jint.Runtime.Interop.Reflection;
using Jint.Runtime.Interpreter;
using Jint.Runtime.Interpreter.Expressions;
using Jint.Runtime.Modules;
using Jint.Runtime.References;

namespace Jint
Expand Down
112 changes: 112 additions & 0 deletions Jint/EsprimaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
using Jint.Runtime.Environments;
using Jint.Runtime.Interpreter;
using Jint.Runtime.Interpreter.Expressions;
using Jint.Runtime.Modules;

namespace Jint
{
Expand Down Expand Up @@ -256,6 +257,117 @@ internal static Record DefineMethod(this ClassProperty m, ObjectInstance obj, Ob
return new Record(property, closure);
}

internal static void GetImportEntries(this ImportDeclaration import, List<ImportEntry> importEntries)
{
var source = import.Source.StringValue;
var specifiers = import.Specifiers;

foreach (var specifier in specifiers)
{
switch (specifier)
{
case ImportNamespaceSpecifier namespaceSpecifier:
importEntries.Add(new ImportEntry(source, "*", namespaceSpecifier.Local.Name));
break;
case ImportSpecifier importSpecifier:
importEntries.Add(new ImportEntry(source, importSpecifier.Imported.Name, importSpecifier.Local.Name));
break;
case ImportDefaultSpecifier defaultSpecifier:
importEntries.Add(new ImportEntry(source, "default", defaultSpecifier.Local.Name));
break;
}
}
}

internal static void GetExportEntries(this ExportDeclaration export, List<ExportEntry> exportEntries)
{
switch (export)
{
case ExportDefaultDeclaration defaultDeclaration:
GetExportEntries(true, defaultDeclaration.Declaration, exportEntries);
break;
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"'
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still?

exportEntries.Add(new(null, allDeclaration.Source.StringValue, "*", null));
break;
case ExportNamedDeclaration namedDeclaration:
var specifiers = namedDeclaration.Specifiers;
if (specifiers.Count == 0)
{
GetExportEntries(false, namedDeclaration.Declaration!, exportEntries, namedDeclaration.Source?.StringValue);
}
else
{
foreach(var specifier in specifiers)
{
exportEntries.Add(new(specifier.Local.Name, namedDeclaration.Source?.StringValue, specifier.Exported.Name, null));
}
}
break;
}
}

private static void GetExportEntries(bool defaultExport, StatementListItem declaration, List<ExportEntry> exportEntries, string? moduleRequest = null)
{
var names = GetExportNames(declaration);

if(names.Count == 0)
{
if (!defaultExport)
{
ExceptionHelper.ThrowTypeErrorNoEngine("export declaration requires an identifier");
}

exportEntries.Add(new("default", null, null, "*default*"));
}
else
{
for(var i = 0; i < names.Count; i++)
{
var name = names[i];
var exportName = defaultExport ? "default" : name;
exportEntries.Add(new(exportName, moduleRequest, null, name));
}
}

}

private static List<string> GetExportNames(StatementListItem declaration)
{
var result = new List<string>();

switch (declaration)
{
case FunctionDeclaration functionDeclaration:
var funcName = functionDeclaration.Id?.Name;
if(funcName is not null)
{
result.Add(funcName);
}
break;
case ClassDeclaration classDeclaration:
var className = classDeclaration.Id?.Name;
if(className is not null)
{
result.Add(className);
}
break;
case VariableDeclaration variableDeclaration:
var declarators = variableDeclaration.Declarations;
foreach(var declarator in declarators)
{
var varName = declarator.Id.As<Identifier>()?.Name;
if(varName is not null)
{
result.Add(varName);
}
}
break;
}

return result;
}

internal readonly struct Record
{
public Record(JsValue key, ScriptFunctionInstance closure)
Expand Down
Loading