diff --git a/bunfig.node-test.toml b/bunfig.node-test.toml index 284945e352..e174c64839 100644 --- a/bunfig.node-test.toml +++ b/bunfig.node-test.toml @@ -2,3 +2,7 @@ # https://github.com/oven-sh/bun/issues/16289 [test] preload = ["./test/js/node/harness.ts", "./test/preload.ts"] + +[install] +# Node.js never auto-installs modules. +auto = "disable" diff --git a/scripts/runner.node.mjs b/scripts/runner.node.mjs index dd360cf4f3..f5d975bf16 100755 --- a/scripts/runner.node.mjs +++ b/scripts/runner.node.mjs @@ -262,7 +262,7 @@ async function runTests() { await runTest(title, async () => { const { ok, error, stdout } = await spawnBun(execPath, { cwd: cwd, - args: [subcommand, "--config=./bunfig.node-test.toml", absoluteTestPath], + args: [subcommand, "--config=" + join(import.meta.dirname, "../bunfig.node-test.toml"), absoluteTestPath], timeout: getNodeParallelTestTimeout(title), env: { FORCE_COLOR: "0", diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 4bc3aef7c8..965ac51adb 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -2110,6 +2110,7 @@ static JSValue constructProcessConfigObject(VM& vm, JSObject* processObject) JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 2); variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i8n_support"_s), JSC::jsNumber(1), 0); variables->putDirect(vm, JSC::Identifier::fromString(vm, "enable_lto"_s), JSC::jsBoolean(false), 0); + variables->putDirect(vm, JSC::Identifier::fromString(vm, "node_module_version"_s), JSC::jsNumber(REPORTED_NODEJS_ABI_VERSION), 0); config->putDirect(vm, JSC::Identifier::fromString(vm, "target_defaults"_s), JSC::constructEmptyObject(globalObject), 0); config->putDirect(vm, JSC::Identifier::fromString(vm, "variables"_s), variables, 0); diff --git a/src/bun.js/bindings/CommonJSModuleRecord.cpp b/src/bun.js/bindings/CommonJSModuleRecord.cpp index 813b332a8c..4ff4743c65 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -384,8 +384,8 @@ JSC_DEFINE_CUSTOM_GETTER(getterParent, (JSC::JSGlobalObject * globalObject, JSC: return JSValue::encode(jsUndefined()); } - if (thisObject->m_overridenParent) { - return JSValue::encode(thisObject->m_overridenParent.get()); + if (thisObject->m_overriddenParent) { + return JSValue::encode(thisObject->m_overriddenParent.get()); } if (thisObject->m_parent) { @@ -400,7 +400,7 @@ JSC_DEFINE_CUSTOM_GETTER(getterParent, (JSC::JSGlobalObject * globalObject, JSC: auto id = idValue->value(globalObject); auto idStr = Bun::toString(id); if (Bun__isBunMain(globalObject, &idStr)) { - thisObject->m_overridenParent.set(globalObject->vm(), thisObject, jsNull()); + thisObject->m_overriddenParent.set(globalObject->vm(), thisObject, jsNull()); return JSValue::encode(jsNull()); } } @@ -494,10 +494,10 @@ JSC_DEFINE_CUSTOM_SETTER(setterParent, if (auto* parent = jsDynamicCast(decodedValue)) { thisObject->m_parent = parent; - thisObject->m_overridenParent.clear(); + thisObject->m_overriddenParent.clear(); } else { thisObject->m_parent = {}; - thisObject->m_overridenParent.set(globalObject->vm(), thisObject, JSValue::decode(value)); + thisObject->m_overriddenParent.set(globalObject->vm(), thisObject, JSValue::decode(value)); } return true; @@ -726,10 +726,10 @@ JSCommonJSModule* JSCommonJSModule::create( if (auto* parentModule = jsDynamicCast(parent)) { out->m_parent = JSC::Weak(parentModule); } else { - out->m_overridenParent.set(vm, out, parent); + out->m_overriddenParent.set(vm, out, parent); } } else if (parent) { - out->m_overridenParent.set(vm, out, parent); + out->m_overriddenParent.set(vm, out, parent); } return out; @@ -1022,7 +1022,7 @@ void JSCommonJSModule::visitChildrenImpl(JSCell* cell, Visitor& visitor) visitor.appendHidden(thisObject->m_filename); visitor.appendHidden(thisObject->m_dirname); visitor.appendHidden(thisObject->m_paths); - visitor.appendHidden(thisObject->m_overridenParent); + visitor.appendHidden(thisObject->m_overriddenParent); } DEFINE_VISIT_CHILDREN(JSCommonJSModule); @@ -1061,8 +1061,8 @@ void JSCommonJSModule::analyzeHeap(JSCell* cell, HeapAnalyzer& analyzer) } } - if (thisObject->m_overridenParent) { - JSValue overridenParent = thisObject->m_overridenParent.get(); + if (thisObject->m_overriddenParent) { + JSValue overridenParent = thisObject->m_overriddenParent.get(); if (overridenParent.isCell()) { const Identifier overridenParentIdentifier = Identifier::fromString(vm, "parent"_s); analyzer.analyzePropertyNameEdge(cell, overridenParent.asCell(), overridenParentIdentifier.impl()); diff --git a/src/bun.js/bindings/CommonJSModuleRecord.h b/src/bun.js/bindings/CommonJSModuleRecord.h index e663dd7460..658b58ef24 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.h +++ b/src/bun.js/bindings/CommonJSModuleRecord.h @@ -46,7 +46,7 @@ public: // // module.parent = parent; // - mutable JSC::WriteBarrier m_overridenParent; + mutable JSC::WriteBarrier m_overriddenParent; // Not visited by the GC. // When the module is assigned a JSCommonJSModule parent, it is assigned to this field. diff --git a/src/bun.js/bindings/NodeModuleModule.bind.ts b/src/bun.js/bindings/NodeModuleModule.bind.ts new file mode 100644 index 0000000000..64e43a9eca --- /dev/null +++ b/src/bun.js/bindings/NodeModuleModule.bind.ts @@ -0,0 +1,8 @@ +import { fn, t } from "bindgen"; + +export const _stat = fn({ + args: { + str: t.UTF8String, + }, + ret: t.i32, +}); diff --git a/src/bun.js/bindings/NodeModuleModule.zig b/src/bun.js/bindings/NodeModuleModule.zig index eaf5e29cb8..03f48fefd9 100644 --- a/src/bun.js/bindings/NodeModuleModule.zig +++ b/src/bun.js/bindings/NodeModuleModule.zig @@ -64,3 +64,12 @@ fn findPathInner( ); return errorable.unwrap() catch null; } + +pub fn _stat(path: []const u8) i32 { + const exists = bun.sys.existsAtType(.cwd(), path).unwrap() catch + return -1; // Returns a negative integer for any other kind of strings. + return switch (exists) { + .file => 0, // Returns 0 for files. + .directory => 1, // Returns 1 for directories. + }; +} diff --git a/src/bun.js/bindings/isBuiltinModule.cpp b/src/bun.js/bindings/isBuiltinModule.cpp index 1d8484618b..dd702fb0cb 100644 --- a/src/bun.js/bindings/isBuiltinModule.cpp +++ b/src/bun.js/bindings/isBuiltinModule.cpp @@ -86,9 +86,16 @@ namespace Bun { bool isBuiltinModule(const String& namePossiblyWithNodePrefix) { String name = namePossiblyWithNodePrefix; - if (name.startsWith("node:"_s)) + if (name.startsWith("node:"_s)) { name = name.substringSharingImpl(5); + // bun doesn't have `node:test` as of writing, but this makes sure that + // `node:module` is compatible (`test/parallel/test-module-isBuiltin.js`) + if (name == "test"_s) { + return true; + } + } + for (auto& builtinModule : builtinModuleNamesSortedLength) { if (name == builtinModule) return true; diff --git a/src/bun.js/module_loader.zig b/src/bun.js/module_loader.zig index 4548299185..4284fd9424 100644 --- a/src/bun.js/module_loader.zig +++ b/src/bun.js/module_loader.zig @@ -1786,6 +1786,24 @@ pub const ModuleLoader = struct { }; } + if (parse_result.empty) { + const was_cjs = (loader == .js or loader == .ts) and brk: { + const ext = std.fs.path.extension(parse_result.source.path.text); + break :brk strings.eqlComptime(ext, ".cjs") or strings.eqlComptime(ext, ".cts"); + }; + if (was_cjs) { + return .{ + .allocator = null, + .source_code = bun.String.static("(function(){})"), + .specifier = input_specifier, + .source_url = input_specifier.createIfDifferent(path.text), + .is_commonjs_module = true, + .hash = 0, + .tag = .javascript, + }; + } + } + if (cache.entry) |*entry| { jsc_vm.source_mappings.putMappings(parse_result.source, .{ .list = .{ .items = @constCast(entry.sourcemap), .capacity = entry.sourcemap.len }, diff --git a/src/bun.js/modules/NodeModuleModule.cpp b/src/bun.js/modules/NodeModuleModule.cpp index c5448fb519..1102ea7d7b 100644 --- a/src/bun.js/modules/NodeModuleModule.cpp +++ b/src/bun.js/modules/NodeModuleModule.cpp @@ -1,23 +1,21 @@ #include "root.h" +#include "headers-handwritten.h" +#include "NodeModuleModule.h" #include - #include - #include - -#include "headers-handwritten.h" +#include +#include +#include #include "PathInlines.h" #include "ZigGlobalObject.h" #include "headers.h" -#include - -#include "NodeModuleModule.h" - #include "ErrorCode.h" -#include -#include + +#include "GeneratedNodeModuleModule.h" + namespace Bun { using namespace JSC; @@ -237,20 +235,22 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire, "createRequire() requires at least one argument"_s); } - auto val = callFrame->uncheckedArgument(0).toWTFString(globalObject); + auto argument = callFrame->uncheckedArgument(0); + auto val = argument.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, {}); - if (val.startsWith("file://"_s)) { + if (!isAbsolutePath(val)) { WTF::URL url(val); if (!url.isValid()) { - throwTypeError(globalObject, scope, - makeString("createRequire() was given an invalid URL '"_s, - url.string(), "'"_s)); + ERR::INVALID_ARG_VALUE(scope, globalObject, + "filename"_s, argument, + "must be a file URL object, file URL string, or absolute path string"_s); RELEASE_AND_RETURN(scope, JSValue::encode({})); } if (!url.protocolIsFile()) { - throwTypeError(globalObject, scope, - "createRequire() does not support non-file URLs"_s); + ERR::INVALID_ARG_VALUE(scope, globalObject, + "filename"_s, argument, + "must be a file URL object, file URL string, or absolute path string"_s); RELEASE_AND_RETURN(scope, JSValue::encode({})); } val = url.fileSystemPath(); @@ -708,6 +708,7 @@ _pathCache getPathCacheObject PropertyCallback _preloadModules jsFunctionPreloadModules Function 0 _resolveFilename nodeModuleResolveFilename CustomAccessor _resolveLookupPaths jsFunctionResolveLookupPaths Function 2 +_stat &Generated::NodeModuleModule::js_stat Function 1 builtinModules getBuiltinModulesObject PropertyCallback constants getConstantsObject PropertyCallback createRequire jsFunctionNodeModuleCreateRequire Function 1 diff --git a/src/bunfig.zig b/src/bunfig.zig index ae8691d90b..7fb47e16d0 100644 --- a/src/bunfig.zig +++ b/src/bunfig.zig @@ -354,7 +354,7 @@ pub const Bunfig = struct { } } - if (comptime cmd.isNPMRelated() or cmd == .RunCommand or cmd == .AutoCommand) { + if (comptime cmd.isNPMRelated() or cmd == .RunCommand or cmd == .AutoCommand or cmd == .TestCommand) { if (json.getObject("install")) |install_obj| { var install: *Api.BunInstall = this.ctx.install orelse brk: { const install = try this.allocator.create(Api.BunInstall); diff --git a/src/resolver/resolver.zig b/src/resolver/resolver.zig index cac7d855c8..4ca990006d 100644 --- a/src/resolver/resolver.zig +++ b/src/resolver/resolver.zig @@ -877,6 +877,13 @@ pub const Resolver = struct { // defer r.mutex.unlock(); errdefer (r.flushDebugLogs(.fail) catch {}); + // A path with a null byte cannot exist on the filesystem. Continuing + // anyways would cause assertion failures. + if (bun.strings.indexOfChar(import_path, 0) != null) { + r.flushDebugLogs(.fail) catch {}; + return .{ .not_found = {} }; + } + var tmp = r.resolveWithoutSymlinks(source_dir_normalized, import_path, kind, global_cache); // Fragments in URLs in CSS imports are technically expected to work @@ -1108,6 +1115,24 @@ pub const Resolver = struct { // Treating these paths as absolute paths on all platforms means Windows // users will not be able to accidentally make use of these paths. if (std.fs.path.isAbsolute(import_path)) { + // Collapse relative directory specifiers if they exist. Extremely + // loose check to avoid always doing this copy, but avoid spending + // too much time on the check. + if (bun.strings.indexOf(import_path, "..") != null) { + const platform = bun.path.Platform.auto; + const ends_with_dir = platform.isSeparator(import_path[import_path.len - 1]) or + (import_path.len > 3 and + platform.isSeparator(import_path[import_path.len - 3]) and + import_path[import_path.len - 2] == '.' and + import_path[import_path.len - 1] == '.'); + const buf = bufs(.relative_abs_path); + import_path = r.fs.absBuf(&.{import_path}, buf); + if (ends_with_dir) { + buf[import_path.len] = platform.separator(); + import_path.len += 1; + } + } + if (r.debug_logs) |*debug| { debug.addNoteFmt("The import \"{s}\" is being treated as an absolute path", .{import_path}); } @@ -3380,89 +3405,65 @@ pub const Resolver = struct { const in_str = try argument.toBunString(globalThis); defer in_str.deref(); - const r = &globalThis.bunVM().transpiler.resolver; - return nodeModulePathsJSValue(r, in_str, globalThis); + return nodeModulePathsJSValue(in_str, globalThis); } pub export fn Resolver__propForRequireMainPaths(globalThis: *bun.JSC.JSGlobalObject) callconv(.C) JSC.JSValue { bun.JSC.markBinding(@src()); const in_str = bun.String.init("."); - const r = &globalThis.bunVM().transpiler.resolver; - return nodeModulePathsJSValue(r, in_str, globalThis); + return nodeModulePathsJSValue(in_str, globalThis); } - pub fn nodeModulePathsJSValue( - r: *ThisResolver, - in_str: bun.String, - globalObject: *bun.JSC.JSGlobalObject, - ) bun.JSC.JSValue { - var list = std.ArrayList(bun.String).init(bun.default_allocator); - defer list.deinit(); - - const sliced = in_str.toUTF8(bun.default_allocator); - defer sliced.deinit(); - - const str = brk: { - if (std.fs.path.isAbsolute(sliced.slice())) { - if (comptime Environment.isWindows) { - const dir_path_buf = bufs(.node_modules_paths_buf); - var normalizer = bun.path.PosixToWinNormalizer{}; - const normalized = normalizer.resolveCWD(sliced.slice()) catch { - @panic("Failed to get cwd for _nodeModulesPaths"); - }; - break :brk bun.path.normalizeBuf(normalized, dir_path_buf, .windows); - } - break :brk sliced.slice(); - } - const dir_path_buf = bufs(.node_modules_paths_buf); - break :brk bun.path.joinStringBuf(dir_path_buf, &[_]string{ r.fs.top_level_dir, sliced.slice() }, .auto); - }; + pub fn nodeModulePathsJSValue(in_str: bun.String, globalObject: *bun.JSC.JSGlobalObject) bun.JSC.JSValue { var arena = std.heap.ArenaAllocator.init(bun.default_allocator); defer arena.deinit(); var stack_fallback_allocator = std.heap.stackFallback(1024, arena.allocator()); const alloc = stack_fallback_allocator.get(); - if (r.readDirInfo(str) catch null) |result| { - var dir_info = result; + var list = std.ArrayList(bun.String).init(alloc); + defer list.deinit(); - while (true) { - const path_without_trailing_slash = strings.withoutTrailingSlash(dir_info.abs_path); - const path_parts = brk: { - if (path_without_trailing_slash.len == 1 and path_without_trailing_slash[0] == '/') { - break :brk [2]string{ "", std.fs.path.sep_str ++ "node_modules" }; - } + const sliced = in_str.toUTF8(bun.default_allocator); + defer sliced.deinit(); + const buf = bufs(.node_modules_paths_buf); - break :brk [2]string{ path_without_trailing_slash, std.fs.path.sep_str ++ "node_modules" }; - }; - const nodemodules_path = bun.strings.concat(alloc, &path_parts) catch unreachable; - bun.path.posixToPlatformInPlace(u8, nodemodules_path); - list.append(bun.String.createUTF8(nodemodules_path)) catch unreachable; - dir_info = (r.readDirInfo(std.fs.path.dirname(path_without_trailing_slash) orelse break) catch null) orelse break; - } - } else { - // does not exist - const full_path = std.fs.path.resolve(r.allocator, &[1][]const u8{str}) catch unreachable; - var path = strings.withoutTrailingSlash(full_path); - while (true) { - const path_without_trailing_slash = strings.withoutTrailingSlash(path); + const full_path = bun.path.joinAbsStringBuf( + bun.fs.FileSystem.instance.top_level_dir, + buf, + &.{sliced.slice()}, + .auto, + ); + const root_index = switch (bun.Environment.os) { + .windows => bun.path.windowsFilesystemRoot(full_path).len, + else => 1, + }; + var root_path: []const u8 = full_path[0..root_index]; + if (full_path.len > root_path.len) { + var it = std.mem.splitBackwardsScalar(u8, full_path[root_index..], std.fs.path.sep); + while (it.next()) |part| { + if (strings.eqlComptime(part, "node_modules")) + continue; - list.append( - bun.String.createUTF8( - bun.strings.concat( - alloc, - &[_]string{ - path_without_trailing_slash, - std.fs.path.sep_str ++ "node_modules", - }, - ) catch unreachable, - ), - ) catch unreachable; - - path = path[0 .. strings.lastIndexOfChar(path, std.fs.path.sep) orelse break]; + list.append(bun.String.createFormat( + "{s}{s}" ++ std.fs.path.sep_str ++ "node_modules", + .{ + root_path, + it.buffer[0 .. (if (it.index) |i| i + 1 else 0) + part.len], + }, + ) catch bun.outOfMemory()) catch bun.outOfMemory(); } } + while (root_path.len > 0 and bun.path.Platform.auto.isSeparator(root_path[root_path.len - 1])) { + root_path.len -= 1; + } + + list.append(bun.String.createFormat( + "{s}" ++ std.fs.path.sep_str ++ "node_modules", + .{root_path}, + ) catch bun.outOfMemory()) catch bun.outOfMemory(); + return bun.String.toJSArray(globalObject, list.items); } diff --git a/src/sys.zig b/src/sys.zig index 47fc5fc250..6af501973c 100644 --- a/src/sys.zig +++ b/src/sys.zig @@ -3367,7 +3367,7 @@ pub fn existsAtType(fd: bun.FileDescriptor, subpath: anytype) Maybe(ExistsAtType if (std.meta.sentinel(@TypeOf(subpath)) == null) { const path_buf = bun.PathBufferPool.get(); defer bun.PathBufferPool.put(path_buf); - @memcpy(path_buf, subpath); + @memcpy(path_buf[0..subpath.len], subpath); path_buf[subpath.len] = 0; const slice: [:0]const u8 = @ptrCast(path_buf); return existsAtType(fd, slice); diff --git a/test/js/node/process/process.test.js b/test/js/node/process/process.test.js index 340dcdd82b..ca300ff834 100644 --- a/test/js/node/process/process.test.js +++ b/test/js/node/process/process.test.js @@ -308,6 +308,7 @@ it("process.config", () => { expect(process.config).toEqual({ variables: { enable_lto: false, + node_module_version: expect.any(Number), v8_enable_i8n_support: 1, }, target_defaults: {}, diff --git a/test/js/node/test/find-new-passes.ts b/test/js/node/test/find-new-passes.ts deleted file mode 100644 index 48c90c33e2..0000000000 --- a/test/js/node/test/find-new-passes.ts +++ /dev/null @@ -1,61 +0,0 @@ -import path from "path"; -import fs from "fs"; -import { spawn } from "child_process"; - -const localDir = path.resolve(import.meta.dirname, "./parallel"); -const upstreamDir = path.resolve(import.meta.dirname, "../../../node.js/upstream/test/parallel"); - -const localFiles = fs.readdirSync(localDir); -const upstreamFiles = fs.readdirSync(upstreamDir); - -const newFiles = upstreamFiles.filter((file) => !localFiles.includes(file)); - -process.on('SIGTERM', () => { - console.log("SIGTERM received"); -}); -process.on('SIGINT', () => { - console.log("SIGINT received"); -}); - -const stdin = process.stdin; -if (stdin.isTTY) { - stdin.setRawMode(true); - stdin.on('data', (data) => { - if (data[0] === 0x03) { - stdin.setRawMode(false); - console.log("Cancelled"); - process.exit(0); - } - }); -} -process.on('exit', () => { - if (stdin.isTTY) { - stdin.setRawMode(false); - } -}); - -for (const file of newFiles) { - await new Promise((resolve, reject) => { - // Run with a timeout of 5 seconds - const proc = spawn("bun-debug", ["run", path.join(upstreamDir, file)], { - timeout: 5000, - stdio: "inherit", - env: { - ...process.env, - BUN_DEBUG_QUIET_LOGS: "1", - }, - }); - - proc.on("error", (err) => { - console.error(err); - }); - - proc.on("exit", (code) => { - if (code === 0) { - console.log(`New Pass: ${file}`); - fs.appendFileSync("new-passes.txt", file + "\n"); - } - resolve(); - }); - }); -} diff --git a/test/js/node/test/parallel/test-dns-channel-timeout.js b/test/js/node/test/parallel/test-dns-channel-timeout.js index 153d7ad907..c7a28451c9 100644 --- a/test/js/node/test/parallel/test-dns-channel-timeout.js +++ b/test/js/node/test/parallel/test-dns-channel-timeout.js @@ -5,10 +5,10 @@ const dgram = require('dgram'); const dns = require('dns'); if (typeof Bun !== 'undefined') { - if (process.platform === 'win32' && require('harness').isCI) { + if (process.platform === 'win32' && require('../../../../harness').isCI) { // TODO(@heimskr): This test mysteriously takes forever in Windows in CI // possibly due to UDP keeping the event loop alive longer than it should. - process.exit(0); + common.skip('Windows CI is flaky'); } } diff --git a/test/js/node/test/parallel/test-module-create-require.js b/test/js/node/test/parallel/test-module-create-require.js new file mode 100644 index 0000000000..30ebf96652 --- /dev/null +++ b/test/js/node/test/parallel/test-module-create-require.js @@ -0,0 +1,32 @@ +'use strict'; + +require('../common'); +const fixtures = require('../common/fixtures'); +const assert = require('assert'); + +const { createRequire } = require('module'); + +const u = fixtures.fileURL('fake.js'); + +const reqToo = createRequire(u); +assert.deepStrictEqual(reqToo('./experimental'), { ofLife: 42 }); + +assert.throws(() => { + createRequire('https://github.com/nodejs/node/pull/27405/'); +}, { + code: 'ERR_INVALID_ARG_VALUE' +}); + +assert.throws(() => { + createRequire('../'); +}, { + code: 'ERR_INVALID_ARG_VALUE' +}); + +assert.throws(() => { + createRequire({}); +}, { + code: 'ERR_INVALID_ARG_VALUE', + message: 'The argument \'filename\' must be a file URL object, file URL ' + + 'string, or absolute path string. Received {}' +}); diff --git a/test/js/node/test/parallel/test-module-isBuiltin.js b/test/js/node/test/parallel/test-module-isBuiltin.js new file mode 100644 index 0000000000..a7815a8dfc --- /dev/null +++ b/test/js/node/test/parallel/test-module-isBuiltin.js @@ -0,0 +1,16 @@ +'use strict'; +require('../common'); +const assert = require('assert'); +const { isBuiltin } = require('module'); + +// Includes modules in lib/ (even deprecated ones) +assert(isBuiltin('http')); +assert(isBuiltin('sys')); +assert(isBuiltin('node:fs')); +assert(isBuiltin('node:test')); + +// Does not include internal modules +assert(!isBuiltin('internal/errors')); +assert(!isBuiltin('test')); +assert(!isBuiltin('')); +assert(!isBuiltin(undefined)); diff --git a/test/js/node/test/parallel/test-module-loading-deprecated.js b/test/js/node/test/parallel/test-module-loading-deprecated.js new file mode 100644 index 0000000000..cc549ba7d2 --- /dev/null +++ b/test/js/node/test/parallel/test-module-loading-deprecated.js @@ -0,0 +1,10 @@ +'use strict'; + +const common = require('../common'); +const assert = require('assert'); + +// common.expectWarning('DeprecationWarning', { +// DEP0128: /^Invalid 'main' field in '.+main[/\\]package\.json' of 'doesnotexist\.js'\..+module author/ +// }); + +assert.strictEqual(require('../fixtures/packages/missing-main').ok, 'ok'); diff --git a/test/js/node/test/parallel/test-module-nodemodulepaths.js b/test/js/node/test/parallel/test-module-nodemodulepaths.js new file mode 100644 index 0000000000..1e355882e0 --- /dev/null +++ b/test/js/node/test/parallel/test-module-nodemodulepaths.js @@ -0,0 +1,127 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +'use strict'; + +const common = require('../common'); +const assert = require('assert'); +const _module = require('module'); + +const cases = { + 'WIN': [{ + file: 'C:\\Users\\hefangshi\\AppData\\Roaming\ +\\npm\\node_modules\\npm\\node_modules\\minimatch', + expect: [ + 'C:\\Users\\hefangshi\\AppData\\Roaming\ +\\npm\\node_modules\\npm\\node_modules\\minimatch\\node_modules', + 'C:\\Users\\hefangshi\\AppData\\Roaming\ +\\npm\\node_modules\\npm\\node_modules', + 'C:\\Users\\hefangshi\\AppData\\Roaming\\npm\\node_modules', + 'C:\\Users\\hefangshi\\AppData\\Roaming\\node_modules', + 'C:\\Users\\hefangshi\\AppData\\node_modules', + 'C:\\Users\\hefangshi\\node_modules', + 'C:\\Users\\node_modules', + 'C:\\node_modules', + ] + }, { + file: 'C:\\Users\\Rocko Artischocko\\node_stuff\\foo', + expect: [ + 'C:\\Users\\Rocko Artischocko\\node_stuff\\foo\\node_modules', + 'C:\\Users\\Rocko Artischocko\\node_stuff\\node_modules', + 'C:\\Users\\Rocko Artischocko\\node_modules', + 'C:\\Users\\node_modules', + 'C:\\node_modules', + ] + }, { + file: 'C:\\Users\\Rocko Artischocko\\node_stuff\\foo_node_modules', + expect: [ + 'C:\\Users\\Rocko \ +Artischocko\\node_stuff\\foo_node_modules\\node_modules', + 'C:\\Users\\Rocko Artischocko\\node_stuff\\node_modules', + 'C:\\Users\\Rocko Artischocko\\node_modules', + 'C:\\Users\\node_modules', + 'C:\\node_modules', + ] + }, { + file: 'C:\\node_modules', + expect: [ + 'C:\\node_modules', + ] + }, { + file: 'C:\\', + expect: [ + 'C:\\node_modules', + ] + }], + 'POSIX': [{ + file: '/usr/lib/node_modules/npm/node_modules/\ +node-gyp/node_modules/glob/node_modules/minimatch', + expect: [ + '/usr/lib/node_modules/npm/node_modules/\ +node-gyp/node_modules/glob/node_modules/minimatch/node_modules', + '/usr/lib/node_modules/npm/node_modules/\ +node-gyp/node_modules/glob/node_modules', + '/usr/lib/node_modules/npm/node_modules/node-gyp/node_modules', + '/usr/lib/node_modules/npm/node_modules', + '/usr/lib/node_modules', + '/usr/node_modules', + '/node_modules', + ] + }, { + file: '/usr/test/lib/node_modules/npm/foo', + expect: [ + '/usr/test/lib/node_modules/npm/foo/node_modules', + '/usr/test/lib/node_modules/npm/node_modules', + '/usr/test/lib/node_modules', + '/usr/test/node_modules', + '/usr/node_modules', + '/node_modules', + ] + }, { + file: '/usr/test/lib/node_modules/npm/foo_node_modules', + expect: [ + '/usr/test/lib/node_modules/npm/foo_node_modules/node_modules', + '/usr/test/lib/node_modules/npm/node_modules', + '/usr/test/lib/node_modules', + '/usr/test/node_modules', + '/usr/node_modules', + '/node_modules', + ] + }, { + file: '/node_modules', + expect: [ + '/node_modules', + ] + }, { + file: '/', + expect: [ + '/node_modules', + ] + }] +}; + +const platformCases = common.isWindows ? cases.WIN : cases.POSIX; +platformCases.forEach((c) => { + const paths = _module._nodeModulePaths(c.file); + assert.deepStrictEqual( + c.expect, paths, + `case ${c.file} failed, actual paths is ${JSON.stringify(paths)}`); +}); diff --git a/test/js/node/test/parallel/test-module-parent-deprecation.js b/test/js/node/test/parallel/test-module-parent-deprecation.js new file mode 100644 index 0000000000..84fd58f0d0 --- /dev/null +++ b/test/js/node/test/parallel/test-module-parent-deprecation.js @@ -0,0 +1,14 @@ +// Flags: --pending-deprecation + +'use strict'; +const common = require('../common'); +const assert = require('assert'); + +// common.expectWarning( +// 'DeprecationWarning', +// 'module.parent is deprecated due to accuracy issues. Please use ' + +// 'require.main to find program entry point instead.', +// 'DEP0144' +// ); + +assert.strictEqual(module.parent, null); diff --git a/test/js/node/test/parallel/test-module-parent-setter-deprecation.js b/test/js/node/test/parallel/test-module-parent-setter-deprecation.js new file mode 100644 index 0000000000..38d4ebfeda --- /dev/null +++ b/test/js/node/test/parallel/test-module-parent-setter-deprecation.js @@ -0,0 +1,13 @@ +// Flags: --pending-deprecation + +'use strict'; +const common = require('../common'); + +// common.expectWarning( +// 'DeprecationWarning', +// 'module.parent is deprecated due to accuracy issues. Please use ' + +// 'require.main to find program entry point instead.', +// 'DEP0144' +// ); + +module.parent = undefined; diff --git a/test/js/node/test/parallel/test-module-stat.js b/test/js/node/test/parallel/test-module-stat.js new file mode 100644 index 0000000000..0ab63f8edf --- /dev/null +++ b/test/js/node/test/parallel/test-module-stat.js @@ -0,0 +1,24 @@ +'use strict'; +require('../common'); + +// This tests Module._stat. + +const Module = require('module'); +const fs = require('fs'); +const tmpdir = require('../common/tmpdir'); +const { ok, strictEqual } = require('assert'); + +const directory = tmpdir.resolve('directory'); +const doesNotExist = tmpdir.resolve('does-not-exist'); +const file = tmpdir.resolve('file.js'); + +tmpdir.refresh(); +fs.writeFileSync(file, "module.exports = { a: 'b' }"); +fs.mkdirSync(directory); + +strictEqual(Module._stat(directory), 1); // Returns 1 for directories. +strictEqual(Module._stat(file), 0); // Returns 0 for files. +ok(Module._stat(doesNotExist) < 0); // Returns a negative integer for any other kind of strings. + +// TODO(RaisinTen): Add tests that make sure that Module._stat() does not crash when called +// with a non-string data type. It crashes currently. diff --git a/test/js/node/test/parallel/test-module-version.js b/test/js/node/test/parallel/test-module-version.js new file mode 100644 index 0000000000..0a8b2427c6 --- /dev/null +++ b/test/js/node/test/parallel/test-module-version.js @@ -0,0 +1,10 @@ +'use strict'; +require('../common'); +const assert = require('assert'); + +// check for existence +assert(Object.hasOwn(process.config.variables, 'node_module_version')); + +// Ensure that `node_module_version` is an Integer > 0 +assert(Number.isInteger(process.config.variables.node_module_version)); +assert(process.config.variables.node_module_version > 0); diff --git a/test/js/node/test/parallel/test-require-enoent-dir.js b/test/js/node/test/parallel/test-require-enoent-dir.js new file mode 100644 index 0000000000..e4db66f985 --- /dev/null +++ b/test/js/node/test/parallel/test-require-enoent-dir.js @@ -0,0 +1,34 @@ +'use strict'; + +const common = require('../common'); +const tmpdir = require('../common/tmpdir'); +const assert = require('assert'); +const fs = require('fs'); +const path = require('path'); + +tmpdir.refresh(); + +const fooPath = tmpdir.resolve('foo.cjs'); +fs.writeFileSync(fooPath, ''); + +const dirPath = tmpdir.resolve('delete_me'); +fs.mkdirSync(dirPath, { + recursive: true, +}); + +const barPath = path.join(dirPath, 'bar.cjs'); +fs.writeFileSync( + barPath, + ` + module.exports = () => require('../foo.cjs').call() +` +); + +const foo = require(fooPath); +console.log('fooPath', fooPath, foo); +const unique = Symbol('unique'); +foo.call = common.mustCall(() => unique); +const bar = require(barPath); + +fs.rmSync(dirPath, { recursive: true }); +assert.strict.equal(bar(), unique); diff --git a/test/js/node/test/parallel/test-require-extension-over-directory.js b/test/js/node/test/parallel/test-require-extension-over-directory.js new file mode 100644 index 0000000000..d1f7407e3d --- /dev/null +++ b/test/js/node/test/parallel/test-require-extension-over-directory.js @@ -0,0 +1,30 @@ +'use strict'; +// Fixes regression from v4 +require('../common'); +const assert = require('assert'); +const fixtures = require('../common/fixtures'); +const path = require('path'); + +const fixturesRequire = require( + fixtures.path('module-extension-over-directory', 'inner')); + +assert.strictEqual( + fixturesRequire, + require(fixtures.path('module-extension-over-directory', 'inner.js')), + 'test-require-extension-over-directory failed to import fixture' + + ' requirements' +); + +const fakePath = [ + fixtures.path('module-extension-over-directory', 'inner'), + 'fake', + '..', +].join(path.sep); +const fixturesRequireDir = require(fakePath); + +assert.strictEqual( + fixturesRequireDir, + require(fixtures.path('module-extension-over-directory', 'inner/')), + 'test-require-extension-over-directory failed to import fixture' + + ' requirements' +); diff --git a/test/js/node/test/parallel/test-require-invalid-package.js b/test/js/node/test/parallel/test-require-invalid-package.js new file mode 100644 index 0000000000..47e22ae939 --- /dev/null +++ b/test/js/node/test/parallel/test-require-invalid-package.js @@ -0,0 +1,9 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Should be an invalid package path. +assert.throws(() => require('package.json'), + { code: 'MODULE_NOT_FOUND' } +); diff --git a/test/js/node/test/parallel/test-require-nul.js b/test/js/node/test/parallel/test-require-nul.js new file mode 100644 index 0000000000..5429f06f75 --- /dev/null +++ b/test/js/node/test/parallel/test-require-nul.js @@ -0,0 +1,11 @@ +'use strict'; + +require('../common'); +const assert = require('assert'); + +// Nul bytes should throw, not abort. +/* eslint-disable no-control-regex */ +assert.throws(() => require('\u0000ab'), /'\u0000ab'/); +assert.throws(() => require('a\u0000b'), /'a\u0000b'/); +assert.throws(() => require('ab\u0000'), /'ab\u0000'/); +/* eslint-enable no-control-regex */ diff --git a/test/js/node/test/tsconfig.json b/test/js/node/test/tsconfig.json new file mode 100644 index 0000000000..b9eac4bd4b --- /dev/null +++ b/test/js/node/test/tsconfig.json @@ -0,0 +1,5 @@ +{ + "compilerOptions": { + "baseUrl": null, + } +} \ No newline at end of file