Skip to content

Commit

Permalink
Script location should be passed to module resolver (#1740)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomatosalat0 authored Jan 13, 2024
1 parent a178818 commit dc20829
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 5 deletions.
129 changes: 129 additions & 0 deletions Jint.Tests/Runtime/ModuleTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Jint.Native;
using Jint.Runtime;
using Jint.Runtime.Modules;

namespace Jint.Tests.Runtime;

Expand Down Expand Up @@ -365,6 +366,134 @@ public void CanReuseModule()
}
}

[Fact]
public void EngineExecutePassesSourceForModuleResolving()
{
var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
{
["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
});
var engine = new Engine(options => options.EnableModules(moduleLoader));
var code = @"
(async () => {
const { value } = await import('./my-module.js');
log(value);
})();
";
List<string> logStatements = new List<string>();
engine.SetValue("log", logStatements.Add);

engine.Execute(code, source: "file:///folder/main.js");
engine.Advanced.ProcessTasks();

Assert.Collection(
logStatements,
s => Assert.Equal("myModuleConst", s));
}

[Fact]
public void EngineExecuteUsesScriptSourceForSource()
{
var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
{
["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
});
var engine = new Engine(options => options.EnableModules(moduleLoader));
var code = @"
(async () => {
const { value } = await import('./my-module.js');
log(value);
})();
";
List<string> logStatements = new List<string>();
engine.SetValue("log", logStatements.Add);

var script = Engine.PrepareScript(code, source: "file:///folder/main.js");
engine.Execute(script);
engine.Advanced.ProcessTasks();

Assert.Collection(
logStatements,
s => Assert.Equal("myModuleConst", s));
}

[Fact]
public void EngineEvaluatePassesSourceForModuleResolving()
{
var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
{
["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
});
var engine = new Engine(options => options.EnableModules(moduleLoader));
var code = @"
(async () => {
const { value } = await import('./my-module.js');
log(value);
})();
";
List<string> logStatements = new List<string>();
engine.SetValue("log", logStatements.Add);

engine.Evaluate(code, source: "file:///folder/main.js");
engine.Advanced.ProcessTasks();

Assert.Collection(
logStatements,
s => Assert.Equal("myModuleConst", s));
}

[Fact]
public void EngineEvaluateUsesScriptSourceForSource()
{
var moduleLoader = new EnforceRelativeModuleLoader(new Dictionary<string, string>()
{
["file:///folder/my-module.js"] = "export const value = 'myModuleConst'"
});
var engine = new Engine(options => options.EnableModules(moduleLoader));
var code = @"
(async () => {
const { value } = await import('./my-module.js');
log(value);
})();
";
List<string> logStatements = new List<string>();
engine.SetValue("log", logStatements.Add);

var script = Engine.PrepareScript(code, source: "file:///folder/main.js");
engine.Evaluate(script);
engine.Advanced.ProcessTasks();

Assert.Collection(
logStatements,
s => Assert.Equal("myModuleConst", s));
}

private sealed class EnforceRelativeModuleLoader : IModuleLoader
{
private readonly IReadOnlyDictionary<string, string> _modules;

public EnforceRelativeModuleLoader(IReadOnlyDictionary<string, string> modules)
{
_modules = modules;
}

public ResolvedSpecifier Resolve(string referencingModuleLocation, ModuleRequest moduleRequest)
{
Assert.False(string.IsNullOrEmpty(referencingModuleLocation), "Referencing module location is null or empty");
var target = new Uri(new Uri(referencingModuleLocation, UriKind.Absolute), moduleRequest.Specifier);
Assert.True(_modules.ContainsKey(target.ToString()), $"Resolve was called with unexpected module request, {moduleRequest.Specifier} relative to {referencingModuleLocation}");
return new ResolvedSpecifier(moduleRequest, target.ToString(), target, SpecifierType.Bare);
}

public Module LoadModule(Engine engine, ResolvedSpecifier resolved)
{
Assert.NotNull(resolved.Uri);
var source = resolved.Uri.ToString();
Assert.True(_modules.TryGetValue(source, out var script), $"Resolved module does not exist: {source}");
return ModuleFactory.BuildSourceTextModule(engine, Engine.PrepareModule(script, source));
}
}

private static string GetBasePath()
{
var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory);
Expand Down
2 changes: 1 addition & 1 deletion Jint/Engine.Modules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ internal ObjectInstance Import(ModuleRequest request, string? referencingModuleL

if (!_modules.TryGetValue(moduleResolution.Key, out var module))
{
module = Load(referencingModuleLocation: null, request);
module = Load(referencingModuleLocation, request);
}

if (module is not CyclicModule cyclicModule)
Expand Down
4 changes: 2 additions & 2 deletions Jint/Engine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ public Engine Execute(string code, string source, ParserOptions parserOptions)
public Engine Execute(Script script)
{
var strict = _isStrict || script.Strict;
ExecuteWithConstraints(strict, () => ScriptEvaluation(new ScriptRecord(Realm, script, string.Empty)));
ExecuteWithConstraints(strict, () => ScriptEvaluation(new ScriptRecord(Realm, script, script.Location.Source)));

return this;
}
Expand Down Expand Up @@ -1594,7 +1594,7 @@ public void Dispose()
clearMethod?.Invoke(_objectWrapperCache, Array.Empty<object>());
#endif
}

[DebuggerDisplay("Engine")]
private sealed class EngineDebugView
{
Expand Down
2 changes: 1 addition & 1 deletion Jint/Runtime/IScriptOrModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ namespace Jint.Runtime;

internal interface IScriptOrModule
{
public string Location { get; }
public string? Location { get; }
}
2 changes: 1 addition & 1 deletion Jint/Runtime/ScriptRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

namespace Jint.Runtime;

internal sealed record ScriptRecord(Realm Realm, Script EcmaScriptCode, string Location) : IScriptOrModule;
internal sealed record ScriptRecord(Realm Realm, Script EcmaScriptCode, string? Location) : IScriptOrModule;

0 comments on commit dc20829

Please sign in to comment.