diff --git a/src/bun.js/api/BunObject.zig b/src/bun.js/api/BunObject.zig index 10030318a2..1b715b2bd8 100644 --- a/src/bun.js/api/BunObject.zig +++ b/src/bun.js/api/BunObject.zig @@ -535,6 +535,22 @@ pub fn inspect(globalThis: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) bun.J return ret; } +export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) ZigString { + // very stable memory address + var array = MutableString.init(getAllocator(globalThis), 0) catch unreachable; + var buffered_writer = MutableString.BufferedWriter{ .context = &array }; + const writer = buffered_writer.writer(); + + var formatter = ConsoleObject.Formatter{ + .globalThis = globalThis, + .quote_strings = true, + }; + writer.print("{}", .{value.toFmt(&formatter)}) catch return ZigString.Empty; + buffered_writer.flush() catch return ZigString.Empty; + + return ZigString.init(array.slice()).withEncoding(); +} + pub fn getInspect(globalObject: *JSC.JSGlobalObject, _: *JSC.JSObject) JSC.JSValue { const fun = JSC.createCallback(globalObject, ZigString.static("inspect"), 2, inspect); var str = ZigString.init("nodejs.util.inspect.custom"); @@ -944,14 +960,11 @@ export fn Bun__resolve(global: *JSGlobalObject, specifier: JSValue, source: JSVa return JSC.JSPromise.resolvedPromiseValue(global, value); } -export fn Bun__resolveSync(global: *JSGlobalObject, specifier: JSValue, source: JSValue, is_esm: bool) JSC.JSValue { - const specifier_str = specifier.toBunString(global); - defer specifier_str.deref(); - +export fn Bun__resolveSync(global: *JSGlobalObject, specifier: *const bun.String, source: JSValue, is_esm: bool) JSC.JSValue { const source_str = source.toBunString(global); defer source_str.deref(); - return JSC.toJSHostValue(global, doResolveWithArgs(global, specifier_str, source_str, is_esm, true)); + return JSC.toJSHostValue(global, doResolveWithArgs(global, specifier.*, source_str, is_esm, true)); } export fn Bun__resolveSyncWithStrings(global: *JSGlobalObject, specifier: *bun.String, source: *bun.String, is_esm: bool) JSC.JSValue { @@ -4566,8 +4579,11 @@ const InternalTestingAPIs = struct { }; comptime { - _ = Crypto.JSPasswordObject.JSPasswordObject__create; - BunObject.exportAll(); + if (Environment.export_cpp_apis) { + _ = Crypto.JSPasswordObject.JSPasswordObject__create; + _ = Bun__inspect; + BunObject.exportAll(); + } } const assert = bun.assert; diff --git a/src/bun.js/bindings/BunProcess.cpp b/src/bun.js/bindings/BunProcess.cpp index 5be9e86cdc..fefe76ada1 100644 --- a/src/bun.js/bindings/BunProcess.cpp +++ b/src/bun.js/bindings/BunProcess.cpp @@ -375,8 +375,7 @@ JSC_DEFINE_HOST_FUNCTION(Process_functionDlopen, #else WTF::String msg = WTF::String::fromUTF8(dlerror()); #endif - JSC::throwTypeError(globalObject, scope, msg); - return {}; + return Bun::ERR::DLOPEN_FAILED(scope, globalObject, msg); } if (callCountAtStart != globalObject->napiModuleRegisterCallCount) { @@ -1868,6 +1867,8 @@ static JSValue constructProcessConfigObject(VM& vm, JSObject* processObject) JSC::JSObject* variables = JSC::constructEmptyObject(globalObject, globalObject->objectPrototype(), 1); variables->putDirect(vm, JSC::Identifier::fromString(vm, "v8_enable_i8n_support"_s), JSC::jsNumber(1), 0); + variables->putDirect(vm, JSC::Identifier::fromString(vm, "node_module_version"_s), + JSC::jsNumber(REPORTED_NODEJS_ABI_VERSION), 0); variables->putDirect(vm, JSC::Identifier::fromString(vm, "enable_lto"_s), JSC::jsBoolean(false), 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 e1d7e0573b..6a9704d469 100644 --- a/src/bun.js/bindings/CommonJSModuleRecord.cpp +++ b/src/bun.js/bindings/CommonJSModuleRecord.cpp @@ -29,6 +29,7 @@ * different value. In that case, it will have a stale value. */ +#include "ErrorCode.h" #include "headers.h" #include "JavaScriptCore/Synchronousness.h" diff --git a/src/bun.js/bindings/ErrorCode.cpp b/src/bun.js/bindings/ErrorCode.cpp index a70a218fa2..ee9abea2dd 100644 --- a/src/bun.js/bindings/ErrorCode.cpp +++ b/src/bun.js/bindings/ErrorCode.cpp @@ -1,4 +1,6 @@ -#include "ErrorCode.h" + +#include "root.h" + #include "ZigGlobalObject.h" #include "DOMException.h" #include "JavaScriptCore/Error.h" @@ -23,6 +25,8 @@ #include "JavaScriptCore/JSInternalFieldObjectImplInlines.h" #include "JSDOMException.h" +#include "ErrorCode.h" + static JSC::JSObject* createErrorPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::ErrorType type, WTF::ASCIILiteral name, WTF::ASCIILiteral code, bool isDOMExceptionPrototype = false) { JSC::JSObject* prototype; @@ -171,6 +175,9 @@ JSObject* createError(Zig::JSGlobalObject* globalObject, ErrorCode code, JSC::JS return createError(vm, globalObject, code, message); } +// export fn Bun__inspect(globalThis: *JSGlobalObject, value: JSValue) ZigString +extern "C" ZigString Bun__inspect(JSC::JSGlobalObject* globalObject, JSValue value); + WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) { ASSERT(!arg.isEmpty()); @@ -179,11 +186,7 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) auto cell = arg.asCell(); switch (cell->type()) { - case JSC::JSType::StringType: { - return arg.toWTFStringForConsole(globalObject); - } case JSC::JSType::SymbolType: { - auto symbol = jsCast(cell); auto result = symbol->tryGetDescriptiveString(); if (result.has_value()) @@ -197,14 +200,14 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) auto name = JSC::getCalculatedDisplayName(vm, cell->getObject()); if (catchScope.exception()) { catchScope.clearException(); - name = "Function"_s; + name = ""_s; } if (!name.isNull() && name.length() > 0) { return makeString("[Function: "_s, name, ']'); } - return "Function"_s; + return "[Function: (anonymous)]"_s; break; } @@ -213,34 +216,132 @@ WTF::String JSValueToStringSafe(JSC::JSGlobalObject* globalObject, JSValue arg) } } - return arg.toWTFStringForConsole(globalObject); + ZigString zstring = Bun__inspect(globalObject, arg); + BunString bstring(BunStringTag::ZigString, BunStringImpl(zstring)); + return bstring.toWTFString(); +} + +WTF::String determineSpecificType(JSC::JSGlobalObject* globalObject, JSValue value) +{ + auto& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + + if (value.isNull()) { + return String("null"_s); + } + if (value.isUndefined()) { + return String("undefined"_s); + } + if (value.isBigInt()) { + auto str = value.toStringOrNull(globalObject); + if (!str) return {}; + return makeString("type bigint ("_s, str->getString(globalObject), "n)"_s); + } + if (value.isNumber()) { + double d = value.asNumber(); + double infinity = std::numeric_limits::infinity(); + if (value == 0) return (1 / d == -infinity) ? String("type number (-0)"_s) : String("type number (0)"_s); + if (d != d) return String("type number (NaN)"_s); + if (d == infinity) return String("type number (Infinity)"_s); + if (d == -infinity) return String("type number (-Infinity)"_s); + auto str = value.toStringOrNull(globalObject); + if (!str) return {}; + return makeString("type number ("_s, str->getString(globalObject), ")"_s); + } + if (value.isBoolean()) { + return value.asBoolean() ? String("type boolean (true)"_s) : String("type boolean (false)"_s); + } + if (value.isSymbol()) { + auto cell = value.asCell(); + auto symbol = jsCast(cell); + auto result = symbol->tryGetDescriptiveString(); + auto description = result.has_value() ? result.value() : String("Symbol()"_s); + return makeString("type symbol ("_s, description, ")"_s); + } + if (value.isCallable()) { + auto& vm = globalObject->vm(); + auto scope = DECLARE_CATCH_SCOPE(vm); + auto cell = value.asCell(); + auto name = JSC::getCalculatedDisplayName(vm, cell->getObject()); + if (scope.exception()) { + scope.clearException(); + name = String(""_s); + } + if (!name.isNull() && name.length() > 0) { + return makeString("function "_s, name); + } + return String("function"_s); + } + if (value.isObject()) { + auto constructor = value.get(globalObject, vm.propertyNames->constructor); + RETURN_IF_EXCEPTION(scope, {}); + if (constructor.toBoolean(globalObject)) { + auto name = constructor.get(globalObject, vm.propertyNames->name); + RETURN_IF_EXCEPTION(scope, {}); + auto str = name.toString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); + return makeString("an instance of "_s, str->getString(globalObject)); + } + // return `${lazyInternalUtilInspect().inspect(value, { depth: -1 })}`; + auto str = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + return str; + } + if (value.isString()) { + auto str = value.toString(globalObject)->getString(globalObject); + if (str.length() > 28) { + str = str.substring(0, 25); + str = makeString(str, "..."_s); + if (!str.contains('\'')) { + return makeString("type string ('"_s, str, "')"_s); + } + } + // return `type string (${JSONStringify(value)})`; + str = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + return makeString("type string ("_s, str, ")"_s); + } + + // value = lazyInternalUtilInspect().inspect(value, { colors: false }); + auto str = JSValueToStringSafe(globalObject, value); + RETURN_IF_EXCEPTION(scope, {}); + return str; } namespace Message { WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, const StringView& arg_name, const StringView& expected_type, JSValue actual_value) { - auto actual_value_string = JSValueToStringSafe(globalObject, actual_value); + auto actual_value_string = determineSpecificType(globalObject, actual_value); RETURN_IF_EXCEPTION(scope, {}); - return makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received: "_s, actual_value_string); + return makeString("The \""_s, arg_name, "\" argument must be of type "_s, expected_type, ". Received "_s, actual_value_string); } WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, const StringView& arg_name, ArgList expected_types, JSValue actual_value) { WTF::StringBuilder result; - auto actual_value_string = JSValueToStringSafe(globalObject, actual_value); + auto actual_value_string = determineSpecificType(globalObject, actual_value); RETURN_IF_EXCEPTION(scope, {}); - result.append("The \""_s, arg_name, "\" argument must be of type "_s); + result.append("The "_s); + + if (arg_name.contains(' ')) { + result.append(arg_name); + } else { + result.append("\""_s); + result.append(arg_name); + result.append("\" argument"_s); + } + result.append(" must be of type "_s); unsigned length = expected_types.size(); if (length == 1) { result.append(expected_types.at(0).toWTFString(globalObject)); } else if (length == 2) { result.append(expected_types.at(0).toWTFString(globalObject)); - result.append(" or "_s); + result.append(", or "_s); result.append(expected_types.at(1).toWTFString(globalObject)); } else { for (unsigned i = 0; i < length - 1; i++) { @@ -249,11 +350,11 @@ WTF::String ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* gl JSValue expected_type = expected_types.at(i); result.append(expected_type.toWTFString(globalObject)); } - result.append(" or "_s); + result.append(", or "_s); result.append(expected_types.at(length - 1).toWTFString(globalObject)); } - result.append(". Received: "_s, actual_value_string); + result.append(". Received "_s, actual_value_string); return result.toString(); } @@ -291,20 +392,26 @@ WTF::String ERR_OUT_OF_RANGE(JSC::ThrowScope& scope, JSC::JSGlobalObject* global auto input = JSValueToStringSafe(globalObject, val_input); RETURN_IF_EXCEPTION(scope, {}); - return makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, range, ". Received: "_s, input); + return makeString("The value of \""_s, arg_name, "\" is out of range. It must be "_s, range, ". Received "_s, input); } } namespace ERR { +JSC::EncodedJSValue throwCode(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, ErrorCode code, const WTF::String& message) +{ + throwScope.throwException(globalObject, createError(globalObject, code, message)); + return {}; +} + JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& arg_name, const WTF::String& expected_type, JSC::JSValue val_actual_value) { - auto arg_kind = arg_name.startsWith("options."_s) ? "property"_s : "argument"_s; + auto arg_kind = String(arg_name).startsWith("options."_s) ? "property"_s : "argument"_s; auto ty_first_char = expected_type[0]; auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s; - auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + auto actual_value = determineSpecificType(globalObject, val_actual_value); RETURN_IF_EXCEPTION(throwScope, {}); auto message = makeString("The \""_s, arg_name, "\" "_s, arg_kind, " must be "_s, ty_kind, " "_s, expected_type, ". Received "_s, actual_value); @@ -320,7 +427,7 @@ JSC::EncodedJSValue INVALID_ARG_TYPE(JSC::ThrowScope& throwScope, JSC::JSGlobalO auto ty_first_char = expected_type[0]; auto ty_kind = ty_first_char >= 'A' && ty_first_char <= 'Z' ? "an instance of"_s : "of type"_s; - auto actual_value = JSValueToStringSafe(globalObject, val_actual_value); + auto actual_value = determineSpecificType(globalObject, val_actual_value); RETURN_IF_EXCEPTION(throwScope, {}); auto message = makeString("The \""_s, arg_name, "\" "_s, arg_kind, " must be "_s, ty_kind, " "_s, expected_type, ". Received "_s, actual_value); @@ -438,8 +545,12 @@ JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalOb return {}; } -JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject) +JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, const WTF::String& name) { + if (!name.isEmpty()) { + throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, makeString("\""_s, name, "\" is outside of buffer bounds"_s))); + return {}; + } throwScope.throwException(globalObject, createError(globalObject, ErrorCode::ERR_BUFFER_OUT_OF_BOUNDS, "Attempt to access memory outside buffer bounds"_s)); return {}; } @@ -469,6 +580,12 @@ JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalOb return {}; } +JSC::EncodedJSValue DLOPEN_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* global, const WTF::String& message) +{ + throwScope.throwException(global, createError(global, ErrorCode::ERR_DLOPEN_FAILED, message)); + return {}; +} + } static JSC::JSValue ERR_INVALID_ARG_TYPE(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue arg0, JSValue arg1, JSValue arg2) diff --git a/src/bun.js/bindings/ErrorCode.h b/src/bun.js/bindings/ErrorCode.h index b3ee8c217c..c36e20f526 100644 --- a/src/bun.js/bindings/ErrorCode.h +++ b/src/bun.js/bindings/ErrorCode.h @@ -92,6 +92,7 @@ JSC::EncodedJSValue STRING_TOO_LONG(JSC::ThrowScope& throwScope, JSC::JSGlobalOb JSC::EncodedJSValue BUFFER_OUT_OF_BOUNDS(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject); JSC::EncodedJSValue UNKNOWN_SIGNAL(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue signal, bool triedUppercase = false); JSC::EncodedJSValue SOCKET_BAD_PORT(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* globalObject, JSC::JSValue name, JSC::JSValue port, bool allowZero); +JSC::EncodedJSValue DLOPEN_FAILED(JSC::ThrowScope& throwScope, JSC::JSGlobalObject* global, const WTF::String& message); } diff --git a/src/bun.js/bindings/ErrorCode.ts b/src/bun.js/bindings/ErrorCode.ts index b9ed12271f..f3e322498f 100644 --- a/src/bun.js/bindings/ErrorCode.ts +++ b/src/bun.js/bindings/ErrorCode.ts @@ -49,6 +49,7 @@ export default [ ["ERR_STREAM_RELEASE_LOCK", Error, "AbortError"], ["ERR_INCOMPATIBLE_OPTION_PAIR", TypeError, "TypeError"], ["ERR_INVALID_URI", URIError, "URIError"], + ["ERR_DLOPEN_FAILED", Error], // Bun-specific ["ERR_FORMDATA_PARSE_ERROR", TypeError], diff --git a/src/bun.js/bindings/ImportMetaObject.cpp b/src/bun.js/bindings/ImportMetaObject.cpp index 61b64f0229..2bf3982eb6 100644 --- a/src/bun.js/bindings/ImportMetaObject.cpp +++ b/src/bun.js/bindings/ImportMetaObject.cpp @@ -1,3 +1,4 @@ +#include "ErrorCode.h" #include "root.h" #include "headers.h" @@ -189,9 +190,13 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje JSC::JSValue moduleName = callFrame->argument(0); JSC::JSValue fromValue = callFrame->argument(1); - if (moduleName.isUndefinedOrNull()) { - JSC::throwTypeError(globalObject, scope, "expects a string"_s); - scope.release(); + if (!moduleName.isString()) { + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "id"_s, "string"_s, moduleName); + return {}; + } + auto moduleNameString = moduleName.toWTFString(globalObject); + if (moduleNameString.isEmpty()) { + Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "id"_s, moduleName, "must be a non-empty string"_s); return {}; } @@ -260,7 +265,8 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObje } } - auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from, isESM); + BunString moduleNameBunString = Bun::toString(moduleNameString); + auto result = Bun__resolveSync(globalObject, &moduleNameBunString, from, isESM); RETURN_IF_EXCEPTION(scope, {}); if (!JSC::JSValue::decode(result).isString()) { @@ -284,9 +290,13 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo JSValue from = callFrame->argument(1); bool isESM = callFrame->argument(2).asBoolean(); - if (moduleName.isUndefinedOrNull()) { - JSC::throwTypeError(lexicalGlobalObject, scope, "expected module name as a string"_s); - scope.release(); + if (!moduleName.isString()) { + Bun::ERR::INVALID_ARG_TYPE(scope, lexicalGlobalObject, "id"_s, "string"_s, moduleName); + return {}; + } + auto moduleNameString = moduleName.toWTFString(globalObject); + if (moduleNameString.isEmpty()) { + Bun::ERR::INVALID_ARG_VALUE(scope, lexicalGlobalObject, "id"_s, moduleName, "must be a non-empty string"_s); return {}; } @@ -294,9 +304,8 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo if (globalObject->onLoadPlugins.hasVirtualModules()) { if (moduleName.isString()) { - auto moduleString = moduleName.toWTFString(globalObject); - if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleString, from.toWTFString(globalObject))) { - if (moduleString == resolvedString.value()) + if (auto resolvedString = globalObject->onLoadPlugins.resolveVirtualModule(moduleNameString, from.toWTFString(globalObject))) { + if (moduleNameString == resolvedString.value()) return JSC::JSValue::encode(moduleName); return JSC::JSValue::encode(jsString(vm, resolvedString.value())); } @@ -331,7 +340,8 @@ extern "C" JSC::EncodedJSValue functionImportMeta__resolveSyncPrivate(JSC::JSGlo } } - auto result = Bun__resolveSync(lexicalGlobalObject, JSC::JSValue::encode(moduleName), JSValue::encode(from), isESM); + BunString moduleNameBunString = Bun::toString(moduleNameString); + auto result = Bun__resolveSync(lexicalGlobalObject, &moduleNameBunString, JSValue::encode(from), isESM); RETURN_IF_EXCEPTION(scope, {}); if (!JSC::JSValue::decode(result).isString()) { diff --git a/src/bun.js/bindings/ImportMetaObject.h b/src/bun.js/bindings/ImportMetaObject.h index 078f8b6b8c..da20aa89dd 100644 --- a/src/bun.js/bindings/ImportMetaObject.h +++ b/src/bun.js/bindings/ImportMetaObject.h @@ -11,7 +11,7 @@ extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSync); extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSyncPrivate); extern "C" JSC::EncodedJSValue Bun__resolve(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm); -extern "C" JSC::EncodedJSValue Bun__resolveSync(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm); +extern "C" JSC::EncodedJSValue Bun__resolveSync(JSC::JSGlobalObject* global, const BunString* specifier, JSC::EncodedJSValue from, bool is_esm); extern "C" JSC::EncodedJSValue Bun__resolveSyncWithSource(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, BunString* from, bool is_esm); extern "C" JSC::EncodedJSValue Bun__resolveSyncWithStrings(JSC::JSGlobalObject* global, BunString* specifier, BunString* from, bool is_esm); diff --git a/src/bun.js/modules/NodeModuleModule.cpp b/src/bun.js/modules/NodeModuleModule.cpp index e913e937b9..00e4f5a830 100644 --- a/src/bun.js/modules/NodeModuleModule.cpp +++ b/src/bun.js/modules/NodeModuleModule.cpp @@ -202,6 +202,10 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionIsBuiltinModule, auto moduleStr = moduleName.toWTFString(globalObject); RETURN_IF_EXCEPTION(scope, JSValue::encode(jsBoolean(false))); + // Bun does not yet implement this module, but to mimic node:module, this special case is needed. + if (moduleStr == "node:test"_s) { + return JSValue::encode(jsBoolean(true)); + } return JSValue::encode(jsBoolean(Bun::isBuiltinModule(moduleStr))); } @@ -329,13 +333,15 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName, JSC::JSValue moduleName = callFrame->argument(0); JSC::JSValue fromValue = callFrame->argument(1); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); if (moduleName.isUndefinedOrNull()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); JSC::throwTypeError(globalObject, scope, "Module._resolveFilename expects a string"_s); scope.release(); return JSC::JSValue::encode(JSC::JSValue {}); } + WTF::String moduleNameString = moduleName.toWTFString(globalObject); + RETURN_IF_EXCEPTION(scope, {}); if ( // fast path: it's a real CommonJS module object. @@ -354,8 +360,8 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName, } } - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); - auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), JSValue::encode(fromValue), false); + BunString moduleNameBunString = Bun::toString(moduleNameString); + auto result = Bun__resolveSync(globalObject, &moduleNameBunString, JSValue::encode(fromValue), false); RETURN_IF_EXCEPTION(scope, {}); if (!JSC::JSValue::decode(result).isString()) { 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-error.js b/test/js/node/test/parallel/test-module-loading-error.js new file mode 100644 index 0000000000..29e0636be0 --- /dev/null +++ b/test/js/node/test/parallel/test-module-loading-error.js @@ -0,0 +1,98 @@ +// 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 { execSync } = require('child_process'); + +const errorMessagesByPlatform = { + win32: ['is not a valid Win32 application'], + linux: ['file too short', 'Exec format error'], + sunos: ['unknown file type', 'not an ELF file'], + darwin: ['file too short', 'not a mach-o file'], + aix: ['Cannot load module', + 'Cannot run a file that does not have a valid format.', + 'Exec format error'], + ibmi: ['Cannot load module', + 'The module has too many section headers', + 'or the file has been truncated.'], +}; +// If we don't know a priori what the error would be, we accept anything. +const platform = common.isIBMi ? 'ibmi' : process.platform; +const errorMessages = errorMessagesByPlatform[platform] || ['']; + +// On Windows, error messages are MUI dependent +// Ref: https://github.com/nodejs/node/issues/13376 +let localeOk = true; +if (common.isWindows) { + const powerShellFindMUI = + 'powershell -NoProfile -ExecutionPolicy Unrestricted -c ' + + '"(Get-UICulture).TwoLetterISOLanguageName"'; + try { + // If MUI != 'en' we'll ignore the content of the message + localeOk = execSync(powerShellFindMUI).toString('utf8').trim() === 'en'; + } catch { + // It's only a best effort try to find the MUI + } +} + +assert.throws( + () => { require('../fixtures/module-loading-error.node'); }, + (e) => { + if (localeOk && !errorMessages.some((msg) => e.message.includes(msg))) + return false; + return e.name === 'Error'; + } +); + +const re = /^The "id" argument must be of type string\. Received /; +[1, false, null, undefined, {}].forEach((value) => { + console.log(value); + assert.throws( + () => { require(value); }, + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_TYPE', + message: re + }); +}); + + +assert.throws( + () => { require(''); }, + { + name: 'TypeError', + code: 'ERR_INVALID_ARG_VALUE', + // Bun uses `"` instead of `'` in util.inspect + message: 'The argument \'id\' must be a non-empty string. Received ""' + }); + +// Folder read operation succeeds in AIX. +// For libuv change, see https://github.com/libuv/libuv/pull/2025. +// https://github.com/nodejs/node/pull/48477#issuecomment-1604586650 +assert.throws( + () => { require('../fixtures/packages/is-dir'); }, + common.isAIX ? { code: 'ERR_INVALID_PACKAGE_CONFIG' } : { + code: 'MODULE_NOT_FOUND', + message: /Cannot find module '\.\.\/fixtures\/packages\/is-dir'/ + } +); 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);