Files
bun.sh/src/codegen/generate-node-errors.ts
taylor.fish 437e15bae5 Replace catch bun.outOfMemory() with safer alternatives (#22141)
Replace `catch bun.outOfMemory()`, which can accidentally catch
non-OOM-related errors, with either `bun.handleOom` or a manual `catch
|err| switch (err)`.

(For internal tracking: fixes STAB-1070)

---------

Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
2025-08-26 12:50:25 -07:00

183 lines
5.6 KiB
TypeScript

import path from "node:path";
import NodeErrors from "../bun.js/bindings/ErrorCode.ts";
import { writeIfNotChanged } from "./helpers.ts";
const outputDir = process.argv[2];
if (!outputDir) {
throw new Error("Missing output directory");
}
const extra_count = NodeErrors.map(x => x.slice(3))
.filter(x => x.length > 0)
.reduce((ac, cv) => ac + cv.length, 0);
const count = NodeErrors.length + extra_count;
if (count > 1 << 16) {
// increase size of the enums below to have more tags
throw new Error(`NodeError can't fit ${count} codes in a u16`);
}
let enumHeader = ``;
let listHeader = ``;
let zig = ``;
enumHeader = `
// clang-format off
// Generated by: src/codegen/generate-node-errors.ts
// Input: src/bun.js/bindings/ErrorCode.ts
#pragma once
#include <cstdint>
namespace Bun {
static constexpr size_t NODE_ERROR_COUNT = ${count};
enum class ErrorCode : uint16_t {
`;
listHeader = `
// clang-format off
// Generated by: src/codegen/generate-node-errors.ts
#pragma once
#include <JavaScriptCore/ErrorType.h>
struct ErrorCodeData {
JSC::ErrorType type;
WTF::ASCIILiteral name;
WTF::ASCIILiteral code;
};
static constexpr ErrorCodeData errors[${count}] = {
`;
zig = `
// Generated by: src/codegen/generate-node-errors.ts
const std = @import("std");
const bun = @import("bun");
const jsc = bun.jsc;
pub fn ErrorBuilder(comptime code: Error, comptime fmt: [:0]const u8, Args: type) type {
return struct {
global: *jsc.JSGlobalObject,
args: Args,
// Throw this error as a JS exception
pub inline fn throw(this: @This()) bun.JSError {
return code.throw(this.global, fmt, this.args);
}
/// Turn this into a JSValue
pub inline fn toJS(this: @This()) jsc.JSValue {
return code.fmt(this.global, fmt, this.args);
}
/// Turn this into a JSPromise that is already rejected.
pub inline fn reject(this: @This()) jsc.JSValue {
if (comptime bun.FeatureFlags.breaking_changes_1_3) {
return jsc.JSPromise.rejectedPromise(this.globalThis, code.fmt(this.global, fmt, this.args)).toJS();
} else {
return jsc.JSPromise.dangerouslyCreateRejectedPromiseValueWithoutNotifyingVM(this.global, code.fmt(this.global, fmt, this.args));
}
}
};
}
pub const Error = enum(u16) {
`;
let i = 0;
for (let [code, constructor, name, ...other_constructors] of NodeErrors) {
if (name == null) name = constructor.name;
// it's useful to avoid the prefix, but module not found has a prefixed and unprefixed version
const codeWithoutPrefix = code === "ERR_MODULE_NOT_FOUND" ? code : code.replace(/^ERR_/, "");
enumHeader += ` ${code} = ${i},\n`;
listHeader += ` { JSC::ErrorType::${constructor.name}, "${name}"_s, "${code}"_s },\n`;
zig += ` /// ${name}: ${code} (instanceof ${constructor.name})\n`;
zig += ` ${codeWithoutPrefix} = ${i},\n`;
i++;
for (const con of other_constructors) {
if (con == null) continue;
if (name == null) name = con.name;
enumHeader += ` ${code}_${con.name} = ${i},\n`;
listHeader += ` { JSC::ErrorType::${con.name}, "${con.name}"_s, "${code}"_s },\n`;
zig += ` /// ${name}: ${code} (instanceof ${con.name})\n`;
zig += ` ${codeWithoutPrefix}_${con.name} = ${i},\n`;
i++;
}
}
enumHeader += `
};
} // namespace Bun
`;
listHeader += `
};
`;
zig += `
extern fn Bun__createErrorWithCode(globalThis: *jsc.JSGlobalObject, code: Error, message: *bun.String) jsc.JSValue;
/// Creates an Error object with the given error code.
/// If an error is thrown while creating the Error object, returns that error instead.
/// Derefs the message string.
pub fn toJS(this: Error, globalThis: *jsc.JSGlobalObject, message: *bun.String) jsc.JSValue {
defer message.deref();
return Bun__createErrorWithCode(globalThis, this, message);
}
pub fn fmt(this: Error, globalThis: *jsc.JSGlobalObject, comptime fmt_str: [:0]const u8, args: anytype) jsc.JSValue {
if (comptime std.meta.fieldNames(@TypeOf(args)).len == 0) {
var message = bun.String.static(fmt_str);
return toJS(this, globalThis, &message);
}
var message = bun.handleOom(bun.String.createFormat(fmt_str, args));
return toJS(this, globalThis, &message);
}
pub fn throw(this: Error, globalThis: *jsc.JSGlobalObject, comptime fmt_str: [:0]const u8, args: anytype) bun.JSError {
return globalThis.throwValue(fmt(this, globalThis, fmt_str, args));
}
};
`;
let builtindtsPath = path.join(import.meta.dir, "..", "..", "src", "js", "builtins.d.ts");
let builtindts = await Bun.file(builtindtsPath).text();
let dts = `
// Generated by: src/codegen/generate-node-errors.ts
// Input: src/bun.js/bindings/ErrorCode.ts
// Global error code functions for TypeScript
`;
for (const [code, constructor, name, ...other_constructors] of NodeErrors) {
const hasExistingOverride = builtindts.includes(`declare function $${code}`);
if (hasExistingOverride) {
continue;
}
const namedError =
name && name !== constructor.name
? `${constructor.name} & { name: "${name}", code: "${code}" }`
: `${constructor.name} & { code: "${code}" }`;
dts += `
/**
* Construct an {@link ${constructor.name} ${constructor.name}} with the \`"${code}"\` error code.
*
* To override this, update ErrorCode.cpp. To remove this generated type, mention \`"${code}"\` in builtins.d.ts.
*/
declare function $${code}(message: string): ${namedError};\n`;
}
writeIfNotChanged(path.join(outputDir, "ErrorCode+List.h"), enumHeader);
writeIfNotChanged(path.join(outputDir, "ErrorCode+Data.h"), listHeader);
writeIfNotChanged(path.join(outputDir, "ErrorCode.zig"), zig);
writeIfNotChanged(path.join(outputDir, "ErrorCode.d.ts"), dts);