Skip to content

Commit

Permalink
Module handling fixes and improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
lahma committed Mar 5, 2022
1 parent fcc7c8d commit 2cf094c
Show file tree
Hide file tree
Showing 23 changed files with 421 additions and 276 deletions.
51 changes: 0 additions & 51 deletions Jint.Tests.Test262/Language/ModuleTestHost.cs

This file was deleted.

49 changes: 3 additions & 46 deletions Jint.Tests.Test262/Language/ModuleTests.cs
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -15,59 +9,22 @@ 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")]
[MemberData(nameof(SourceFiles), "language\\export", false)]
[MemberData(nameof(SourceFiles), "language\\export", true, Skip = "Skipped")]
protected void Export(SourceFile sourceFile)
{
RunModuleTest(sourceFile);
RunTestInternal(sourceFile);
}

[Theory(DisplayName = "language\\import")]
[MemberData(nameof(SourceFiles), "language\\import", false)]
[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);
}
}
12 changes: 11 additions & 1 deletion Jint.Tests.Test262/Test262Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ protected void RunTestCode(string fileName, string code, bool strict)
var engine = new Engine(cfg => cfg
.LocalTimeZone(_pacificTimeZone)
.Strict(strict)
.EnableModules(Path.Combine(BasePath, "test", Path.GetDirectoryName(fileName)!))
);

engine.Execute(Sources["sta.js"]);
Expand Down Expand Up @@ -151,6 +152,8 @@ protected void RunTestCode(string fileName, string code, bool strict)
engine.Execute(Sources[file.Trim()]);
}
}

var module = Regex.IsMatch(code, @"flags:\s*?\[.*?module.*?]");

if (code.IndexOf("propertyHelper.js", StringComparison.OrdinalIgnoreCase) != -1)
{
Expand All @@ -162,7 +165,14 @@ protected void RunTestCode(string fileName, string code, bool strict)
bool negative = code.IndexOf("negative:", StringComparison.Ordinal) > -1;
try
{
engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseScript());
if (module)
{
engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseModule());
}
else
{
engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseScript());
}
}
catch (JavaScriptException j)
{
Expand Down
19 changes: 19 additions & 0 deletions Jint.Tests/Runtime/ModuleTests.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#if(NET6_0_OR_GREATER)
using System.IO;
using System.Reflection;
using Jint.Runtime.Modules;
#endif
using System;
using Esprima;
using Jint.Native;
using Jint.Runtime;
using Xunit;
Expand Down Expand Up @@ -234,6 +236,23 @@ public void CanImportFromFile()

Assert.Equal("John Doe", result);
}

[Fact]
public void CanReferenceModuleImportFromFileInInlineScript()
{
var engine = new Engine(options => options.EnableModules(GetBasePath()));
const string script = @"
import { formatName } from './modules/format-name.js'
formatName('John', 'Doe');
";

var ex = Assert.Throws<JavaScriptException>(() => engine.Evaluate(script));
Assert.Equal("Cannot use import/export statements outside a module", ex.Message);

var value = engine.Evaluate(script, SourceType.Module);
Assert.IsType<ModuleNamespace>(value);
}


private static string GetBasePath()
{
Expand Down
32 changes: 23 additions & 9 deletions Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using Esprima;
using Esprima.Ast;
using Jint.Native;
using Jint.Native.Object;
using Jint.Native.Promise;
Expand Down Expand Up @@ -41,7 +42,7 @@ internal JsModule LoadModule(string? referencingModuleLocation, string specifier
if (_builders.TryGetValue(specifier, out var moduleBuilder))
{
var parsedModule = moduleBuilder.Parse();
module = new JsModule(this, _host.CreateRealm(), parsedModule, null, false);
module = new JsModule(this, Realm, parsedModule, null, false);
// Early link is required because we need to bind values before returning
module.Link();
moduleBuilder.BindExportedValues(module);
Expand All @@ -50,7 +51,7 @@ internal JsModule LoadModule(string? referencingModuleLocation, string specifier
else
{
var parsedModule = ModuleLoader.LoadModule(this, moduleResolution);
module = new JsModule(this, _host.CreateRealm(), parsedModule, moduleResolution.Uri?.LocalPath, false);
module = new JsModule(this, Realm, parsedModule, moduleResolution.Uri?.LocalPath, false);
}

_modules[moduleResolution.Key] = module;
Expand Down Expand Up @@ -79,13 +80,30 @@ public void AddModule(string specifier, ModuleBuilder moduleBuilder)

public ObjectInstance ImportModule(string specifier)
{
var moduleResolution = ModuleLoader.Resolve(null, specifier);
var moduleResolution = ModuleLoader.Resolve(referencingModuleLocation: null, specifier);

if (!_modules.TryGetValue(moduleResolution.Key, out var module))
{
module = LoadModule(null, specifier);
}

return Execute(specifier, module);
}

public Engine Execute(Module module)
{
Evaluate(module);
return this;
}

public JsValue Evaluate(Module module)
{
var jsModule = new JsModule(this, Realm, module, location: null, async: false);
return Execute(specifier: null, jsModule);
}

private ObjectInstance Execute(string? specifier, JsModule module)
{
if (module.Status == ModuleStatus.Unlinked)
{
module.Link();
Expand All @@ -108,11 +126,7 @@ public ObjectInstance ImportModule(string specifier)
}
}

if (evaluationResult == null)
{
ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise");
}
else if (evaluationResult is not PromiseInstance promise)
if (evaluationResult is not PromiseInstance promise)
{
ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}");
}
Expand All @@ -129,7 +143,7 @@ public ObjectInstance ImportModule(string specifier)
if (module.Status == ModuleStatus.Evaluated)
{
// TODO what about callstack and thrown exceptions?
RunAvailableContinuations(_eventLoop);
RunAvailableContinuations();

return JsModule.GetModuleNamespace(module);
}
Expand Down
29 changes: 16 additions & 13 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -239,20 +239,24 @@ public void ResetCallStack()
CallStack.Clear();
}

public JsValue Evaluate(string source)
=> Execute(source, DefaultParserOptions)._completionValue;
public JsValue Evaluate(string source, SourceType sourceType = SourceType.Script)
=> Evaluate(source, DefaultParserOptions, sourceType);

public JsValue Evaluate(string source, ParserOptions parserOptions)
=> Execute(source, parserOptions)._completionValue;
public JsValue Evaluate(string source, ParserOptions parserOptions, SourceType sourceType = SourceType.Script)
=> sourceType == SourceType.Script
? Evaluate(new JavaScriptParser(source, parserOptions).ParseScript())
: Evaluate(new JavaScriptParser(source, parserOptions).ParseModule());

public JsValue Evaluate(Script script)
=> Execute(script)._completionValue;

public Engine Execute(string source)
=> Execute(source, DefaultParserOptions);
public Engine Execute(string source, SourceType sourceType = SourceType.Script)
=> Execute(source, DefaultParserOptions, sourceType);

public Engine Execute(string source, ParserOptions parserOptions)
=> Execute(new JavaScriptParser(source, parserOptions).ParseScript());
public Engine Execute(string source, ParserOptions parserOptions, SourceType sourceType = SourceType.Script)
=> sourceType == SourceType.Script
? Execute(new JavaScriptParser(source, parserOptions).ParseScript())
: Execute(new JavaScriptParser(source, parserOptions).ParseModule());

public Engine Execute(Script script)
{
Expand Down Expand Up @@ -284,7 +288,7 @@ Engine DoInvoke()
}

// TODO what about callstack and thrown exceptions?
RunAvailableContinuations(_eventLoop);
RunAvailableContinuations();

_completionValue = result.GetValueOrDefault();

Expand Down Expand Up @@ -320,7 +324,7 @@ public ManualPromise RegisterPromise()
Action<JsValue> SettleWith(FunctionInstance settle) => value =>
{
settle.Call(JsValue.Undefined, new[] {value});
RunAvailableContinuations(_eventLoop);
RunAvailableContinuations();
};

return new ManualPromise(promise, SettleWith(resolve), SettleWith(reject));
Expand All @@ -331,10 +335,9 @@ internal void AddToEventLoop(Action continuation)
_eventLoop.Events.Enqueue(continuation);
}


private static void RunAvailableContinuations(EventLoop loop)
internal void RunAvailableContinuations()
{
var queue = loop.Events;
var queue = _eventLoop.Events;

while (true)
{
Expand Down
10 changes: 6 additions & 4 deletions Jint/EsprimaExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ internal static void GetExportEntries(this ExportDeclaration export, List<Export
exportEntries.Add(new(null, 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);
Expand All @@ -317,8 +317,9 @@ internal static void GetExportEntries(this ExportDeclaration export, List<Export
}
else
{
foreach (var specifier in specifiers)
for (var i = 0; i < specifiers.Count; i++)
{
var specifier = specifiers[i];
exportEntries.Add(new(specifier.Local.GetModuleKey(), namedDeclaration.Source?.StringValue, specifier.Exported.GetModuleKey(), null));
}
}
Expand Down Expand Up @@ -372,9 +373,10 @@ private static List<string> GetExportNames(StatementListItem declaration)

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)
{
Expand Down
2 changes: 1 addition & 1 deletion Jint/HoistingScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private HoistingScope(
}

public static HoistingScope GetProgramLevelDeclarations(
Script script,
Program script,
bool collectVarNames = false,
bool collectLexicalNames = false)
{
Expand Down
2 changes: 1 addition & 1 deletion Jint/ModuleBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public sealed class ModuleBuilder
private readonly List<string> _sourceRaw = new();
private readonly Dictionary<string, JsValue> _exports = new();

public ModuleBuilder(Engine engine)
internal ModuleBuilder(Engine engine)
{
_engine = engine;
}
Expand Down
Loading

0 comments on commit 2cf094c

Please sign in to comment.