소스 검색

Script location should be passed to module resolver (#1740)

tomatosalat0 1 년 전
부모
커밋
dc20829be5
5개의 변경된 파일134개의 추가작업 그리고 5개의 파일을 삭제
  1. 129 0
      Jint.Tests/Runtime/ModuleTests.cs
  2. 1 1
      Jint/Engine.Modules.cs
  3. 2 2
      Jint/Engine.cs
  4. 1 1
      Jint/Runtime/IScriptOrModule.cs
  5. 1 1
      Jint/Runtime/ScriptRecord.cs

+ 129 - 0
Jint.Tests/Runtime/ModuleTests.cs

@@ -1,5 +1,6 @@
 using Jint.Native;
 using Jint.Runtime;
+using Jint.Runtime.Modules;
 
 namespace Jint.Tests.Runtime;
 
@@ -365,6 +366,134 @@ export const count = globals.counter;
         }
     }
 
+    [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);

+ 1 - 1
Jint/Engine.Modules.cs

@@ -113,7 +113,7 @@ public partial class Engine
 
             if (!_modules.TryGetValue(moduleResolution.Key, out var module))
             {
-                module = Load(referencingModuleLocation: null, request);
+                module = Load(referencingModuleLocation, request);
             }
 
             if (module is not CyclicModule cyclicModule)

+ 2 - 2
Jint/Engine.cs

@@ -376,7 +376,7 @@ namespace Jint
         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;
         }
@@ -1594,7 +1594,7 @@ namespace Jint
             clearMethod?.Invoke(_objectWrapperCache, Array.Empty<object>());
 #endif
         }
-        
+
         [DebuggerDisplay("Engine")]
         private sealed class EngineDebugView
         {

+ 1 - 1
Jint/Runtime/IScriptOrModule.cs

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

+ 1 - 1
Jint/Runtime/ScriptRecord.cs

@@ -2,4 +2,4 @@ using Esprima.Ast;
 
 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;