diff --git a/Jint.Tests.PublicInterface/ShadowRealmTests.cs b/Jint.Tests.PublicInterface/ShadowRealmTests.cs index f0d062d506..9c7f7ba2ff 100644 --- a/Jint.Tests.PublicInterface/ShadowRealmTests.cs +++ b/Jint.Tests.PublicInterface/ShadowRealmTests.cs @@ -35,7 +35,7 @@ public void MultipleShadowRealmsDoNotInterfere() engine.SetValue("message", "world"); engine.Evaluate("function hello() {return message}"); - Assert.Equal("world",engine.Evaluate("hello();")); + Assert.Equal("world", engine.Evaluate("hello();")); var shadowRealm = engine.Realm.Intrinsics.ShadowRealm.Construct(); shadowRealm.SetValue("message", "realm 1"); @@ -97,9 +97,25 @@ private static string GetBasePath() var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory); var current = assemblyDirectory; - while (current is not null && current.GetDirectories().All(x => x.Name != "Jint.Tests")) + var binDirectory = $"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"; + while (current is not null) { - current = current.Parent; + if (current.FullName.Contains(binDirectory) || current.Name == "bin") + { + current = current.Parent; + continue; + } + + var testDirectory = current.GetDirectories("Jint.Tests").FirstOrDefault(); + if (testDirectory == null) + { + current = current.Parent; + continue; + } + + // found it + current = testDirectory; + break; } if (current is null) @@ -107,6 +123,6 @@ private static string GetBasePath() throw new NullReferenceException($"Could not find tests base path, assemblyPath: {assemblyDirectory}"); } - return Path.Combine(current.FullName, "Jint.Tests", "Runtime", "Scripts"); + return Path.Combine(current.FullName, "Runtime", "Scripts"); } } diff --git a/Jint.Tests/Runtime/ModuleTests.cs b/Jint.Tests/Runtime/ModuleTests.cs index be4736b68c..a022644394 100644 --- a/Jint.Tests/Runtime/ModuleTests.cs +++ b/Jint.Tests/Runtime/ModuleTests.cs @@ -15,7 +15,7 @@ public ModuleTests() [Fact] public void ShouldExportNamed() { - _engine.AddModule("my-module", @"export const value = 'exported value';"); + _engine.AddModule("my-module", "export const value = 'exported value';"); var ns = _engine.ImportModule("my-module"); Assert.Equal("exported value", ns.Get("value").AsString()); @@ -24,7 +24,7 @@ public void ShouldExportNamed() [Fact] public void ShouldExportNamedListRenamed() { - _engine.AddModule("my-module", @"const value1 = 1; const value2 = 2; export { value1 as renamed1, value2 as renamed2 }"); + _engine.AddModule("my-module", "const value1 = 1; const value2 = 2; export { value1 as renamed1, value2 as renamed2 }"); var ns = _engine.ImportModule("my-module"); Assert.Equal(1, ns.Get("renamed1").AsInteger()); @@ -34,7 +34,7 @@ public void ShouldExportNamedListRenamed() [Fact] public void ShouldExportDefault() { - _engine.AddModule("my-module", @"export default 'exported value';"); + _engine.AddModule("my-module", "export default 'exported value';"); var ns = _engine.ImportModule("my-module"); Assert.Equal("exported value", ns.Get("default").AsString()); @@ -43,8 +43,8 @@ public void ShouldExportDefault() [Fact] public void ShouldExportAll() { - _engine.AddModule("module1", @"export const value = 'exported value';"); - _engine.AddModule("module2", @"export * from 'module1';"); + _engine.AddModule("module1", "export const value = 'exported value';"); + _engine.AddModule("module2", "export * from 'module1';"); var ns = _engine.ImportModule("module2"); Assert.Equal("exported value", ns.Get("value").AsString()); @@ -53,8 +53,8 @@ public void ShouldExportAll() [Fact] public void ShouldImportNamed() { - _engine.AddModule("imported-module", @"export const value = 'exported value';"); - _engine.AddModule("my-module", @"import { value } from 'imported-module'; export const exported = value;"); + _engine.AddModule("imported-module", "export const value = 'exported value';"); + _engine.AddModule("my-module", "import { value } from 'imported-module'; export const exported = value;"); var ns = _engine.ImportModule("my-module"); Assert.Equal("exported value", ns.Get("exported").AsString()); @@ -63,8 +63,8 @@ public void ShouldImportNamed() [Fact] public void ShouldImportRenamed() { - _engine.AddModule("imported-module", @"export const value = 'exported value';"); - _engine.AddModule("my-module", @"import { value as renamed } from 'imported-module'; export const exported = renamed;"); + _engine.AddModule("imported-module", "export const value = 'exported value';"); + _engine.AddModule("my-module", "import { value as renamed } from 'imported-module'; export const exported = renamed;"); var ns = _engine.ImportModule("my-module"); Assert.Equal("exported value", ns.Get("exported").AsString()); @@ -73,8 +73,8 @@ public void ShouldImportRenamed() [Fact] public void ShouldImportDefault() { - _engine.AddModule("imported-module", @"export default 'exported value';"); - _engine.AddModule("my-module", @"import imported from 'imported-module'; export const exported = imported;"); + _engine.AddModule("imported-module", "export default 'exported value';"); + _engine.AddModule("my-module", "import imported from 'imported-module'; export const exported = imported;"); var ns = _engine.ImportModule("my-module"); Assert.Equal("exported value", ns.Get("exported").AsString()); @@ -83,8 +83,8 @@ public void ShouldImportDefault() [Fact] public void ShouldImportAll() { - _engine.AddModule("imported-module", @"export const value = 'exported value';"); - _engine.AddModule("my-module", @"import * as imported from 'imported-module'; export const exported = imported.value;"); + _engine.AddModule("imported-module", "export const value = 'exported value';"); + _engine.AddModule("my-module", "import * as imported from 'imported-module'; export const exported = imported.value;"); var ns = _engine.ImportModule("my-module"); Assert.Equal("exported value", ns.Get("exported").AsString()); @@ -95,7 +95,7 @@ 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.AddModule("my-module", "import('imported-module').then(ns => { ns.signal(); });"); _engine.ImportModule("my-module"); @@ -105,8 +105,8 @@ public void ShouldImportDynamically() [Fact] public void ShouldPropagateParseError() { - _engine.AddModule("imported", @"export const invalid;"); - _engine.AddModule("my-module", @"import { invalid } from 'imported';"); + _engine.AddModule("imported", "export const invalid;"); + _engine.AddModule("my-module", "import { invalid } from 'imported';"); var exc = Assert.Throws(() => _engine.ImportModule("my-module")); Assert.Equal("Error while loading module: error in module 'imported': Line 1: Missing initializer in const declaration", exc.Message); @@ -116,8 +116,8 @@ public void ShouldPropagateParseError() [Fact] public void ShouldPropagateLinkError() { - _engine.AddModule("imported", @"export invalid;"); - _engine.AddModule("my-module", @"import { value } from 'imported';"); + _engine.AddModule("imported", "export invalid;"); + _engine.AddModule("my-module", "import { value } from 'imported';"); var exc = Assert.Throws(() => _engine.ImportModule("my-module")); Assert.Equal("Error while loading module: error in module 'imported': Line 1: Unexpected identifier", exc.Message); @@ -127,7 +127,7 @@ public void ShouldPropagateLinkError() [Fact] public void ShouldPropagateExecuteError() { - _engine.AddModule("my-module", @"throw new Error('imported successfully');"); + _engine.AddModule("my-module", "throw new Error('imported successfully');"); var exc = Assert.Throws(() => _engine.ImportModule("my-module")); Assert.Equal("imported successfully", exc.Message); @@ -137,8 +137,8 @@ public void ShouldPropagateExecuteError() [Fact] public void ShouldPropagateThrowStatementThroughJavaScriptImport() { - _engine.AddModule("imported-module", @"throw new Error('imported successfully');"); - _engine.AddModule("my-module", @"import 'imported-module';"); + _engine.AddModule("imported-module", "throw new Error('imported successfully');"); + _engine.AddModule("my-module", "import 'imported-module';"); var exc = Assert.Throws(() => _engine.ImportModule("my-module")); Assert.Equal("imported successfully", exc.Message); @@ -156,8 +156,11 @@ public void ShouldAddModuleFromJsValue() [Fact] public void ShouldAddModuleFromClrInstance() { - _engine.AddModule("imported-module", builder => builder.ExportObject("value", new ImportedClass { Value = "instance value" })); - _engine.AddModule("my-module", @"import { value } from 'imported-module'; export const exported = value.value;"); + _engine.AddModule("imported-module", builder => builder.ExportObject("value", new ImportedClass + { + Value = "instance value" + })); + _engine.AddModule("my-module", "import { value } from 'imported-module'; export const exported = value.value;"); var ns = _engine.ImportModule("my-module"); Assert.Equal("instance value", ns.Get("exported").AsString()); @@ -178,7 +181,7 @@ public void ShouldAllowInvokeUserDefinedClass() public void ShouldAddModuleFromClrType() { _engine.AddModule("imported-module", builder => builder.ExportType()); - _engine.AddModule("my-module", @"import { ImportedClass } from 'imported-module'; export const exported = new ImportedClass().value;"); + _engine.AddModule("my-module", "import { ImportedClass } from 'imported-module'; export const exported = new ImportedClass().value;"); var ns = _engine.ImportModule("my-module"); Assert.Equal("hello world", ns.Get("exported").AsString()); @@ -207,8 +210,20 @@ public void ShouldAddModuleFromClrFunction() 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()); + 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 @@ -222,7 +237,7 @@ public void ShouldAllowExportMultipleImports() _engine.AddModule("@mine/import1", builder => builder.ExportValue("value1", JsNumber.Create(1))); _engine.AddModule("@mine/import2", builder => builder.ExportValue("value2", JsNumber.Create(2))); _engine.AddModule("@mine", "export * from '@mine/import1'; export * from '@mine/import2'"); - _engine.AddModule("app", @"import { value1, value2 } from '@mine'; export const result = `${value1} ${value2}`"); + _engine.AddModule("app", "import { value1, value2 } from '@mine'; export const result = `${value1} ${value2}`"); var ns = _engine.ImportModule("app"); Assert.Equal("1 2", ns.Get("result").AsString()); @@ -258,7 +273,7 @@ public void ShouldImportOnlyOnce() { var called = 0; _engine.AddModule("imported-module", builder => builder.ExportFunction("count", args => called++)); - _engine.AddModule("my-module", @"import { count } from 'imported-module'; count();"); + _engine.AddModule("my-module", "import { count } from 'imported-module'; count();"); _engine.ImportModule("my-module"); _engine.ImportModule("my-module"); @@ -268,14 +283,14 @@ public void ShouldImportOnlyOnce() [Fact] public void ShouldAllowSelfImport() { - _engine.AddModule("my-globals", @"export const globals = { counter: 0 };"); + _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"); + var ns = _engine.ImportModule("my-module"); Assert.Equal(1, ns.Get("count").AsInteger()); } @@ -285,8 +300,8 @@ 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';"); + _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"); @@ -301,7 +316,7 @@ public void ShouldSupportConstraints() var engine = new Engine(opts => opts.TimeoutInterval(TimeSpan.FromTicks(1))); engine.AddModule("sleep", builder => builder.ExportFunction("sleep", () => Thread.Sleep(100))); - engine.AddModule("my-module", @"import { sleep } from 'sleep'; for(var i = 0; i < 100; i++) { sleep(); } export const result = 'ok';"); + engine.AddModule("my-module", "import { sleep } from 'sleep'; for(var i = 0; i < 100; i++) { sleep(); } export const result = 'ok';"); Assert.Throws(() => engine.ImportModule("my-module")); } @@ -350,14 +365,30 @@ public void CanReuseModule() } } - internal static string GetBasePath() + private static string GetBasePath() { var assemblyDirectory = new DirectoryInfo(AppDomain.CurrentDomain.RelativeSearchPath ?? AppDomain.CurrentDomain.BaseDirectory); var current = assemblyDirectory; - while (current is not null && current.Name != "Jint.Tests") + var binDirectory = $"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"; + while (current is not null) { - current = current.Parent; + if (current.FullName.Contains(binDirectory) || current.Name == "bin") + { + current = current.Parent; + continue; + } + + var testDirectory = current.GetDirectories("Jint.Tests").FirstOrDefault(); + if (testDirectory == null) + { + current = current.Parent; + continue; + } + + // found it + current = testDirectory; + break; } if (current is null) diff --git a/Jint/Runtime/ExceptionHelper.cs b/Jint/Runtime/ExceptionHelper.cs index ea71268655..e47bb5a518 100644 --- a/Jint/Runtime/ExceptionHelper.cs +++ b/Jint/Runtime/ExceptionHelper.cs @@ -208,9 +208,9 @@ public static void ThrowExecutionCanceledException() } [DoesNotReturn] - public static void ThrowModuleResolutionException(string resolverAlgorithmError, string specifier, string? parent) + public static void ThrowModuleResolutionException(string message, string specifier, string? parent, string? filePath = null) { - throw new ModuleResolutionException(resolverAlgorithmError, specifier, parent); + throw new ModuleResolutionException(message, specifier, parent, filePath); } } } diff --git a/Jint/Runtime/Modules/DefaultModuleLoader.cs b/Jint/Runtime/Modules/DefaultModuleLoader.cs index b019f8c54e..c90acc0bfb 100644 --- a/Jint/Runtime/Modules/DefaultModuleLoader.cs +++ b/Jint/Runtime/Modules/DefaultModuleLoader.cs @@ -8,12 +8,7 @@ public sealed class DefaultModuleLoader : IModuleLoader private readonly Uri _basePath; private readonly bool _restrictToBasePath; - public DefaultModuleLoader(string basePath) : this(basePath, true) - { - - } - - public DefaultModuleLoader(string basePath, bool restrictToBasePath) + public DefaultModuleLoader(string basePath, bool restrictToBasePath = true) { if (string.IsNullOrWhiteSpace(basePath)) { @@ -74,7 +69,7 @@ public ResolvedSpecifier Resolve(string? referencingModuleLocation, string speci return new ResolvedSpecifier( specifier, specifier, - null, + Uri: null, SpecifierType.Bare ); } @@ -120,10 +115,11 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved) { ExceptionHelper.ThrowInvalidOperationException($"Module '{resolved.Specifier}' of type '{resolved.Type}' has no resolved URI."); } + var fileName = Uri.UnescapeDataString(resolved.Uri.AbsolutePath); if (!File.Exists(fileName)) { - ExceptionHelper.ThrowArgumentException("Module Not Found: ", resolved.Specifier); + ExceptionHelper.ThrowModuleResolutionException("Module Not Found", resolved.Specifier, parent: null, fileName); return default; } diff --git a/Jint/Runtime/Modules/ModuleResolutionException.cs b/Jint/Runtime/Modules/ModuleResolutionException.cs index 2da629c985..f392b2abee 100644 --- a/Jint/Runtime/Modules/ModuleResolutionException.cs +++ b/Jint/Runtime/Modules/ModuleResolutionException.cs @@ -2,13 +2,15 @@ namespace Jint.Runtime.Modules; public sealed class ModuleResolutionException : JintException { - public string ResolverAlgorithmError { get; } - public string Specifier { get; } - - public ModuleResolutionException(string message, string specifier, string? parent) - : base($"{message} in module '{parent ?? "(null)"}': '{specifier}'") + public ModuleResolutionException(string resolverAlgorithmError, string specifier, string? parent, string? filePath) + : base($"{resolverAlgorithmError} in module '{parent ?? "(null)"}': '{specifier}'") { - ResolverAlgorithmError = message; + ResolverAlgorithmError = resolverAlgorithmError; Specifier = specifier; + FilePath = filePath; } -} \ No newline at end of file + + public string ResolverAlgorithmError { get; } + public string Specifier { get; } + public string? FilePath { get; } +}