mirror of
https://github.com/oven-sh/bun
synced 2026-02-19 23:31:45 +00:00
Compare commits
2 Commits
claude/fix
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3aa7964f9a | ||
|
|
e99608620c |
@@ -152,18 +152,16 @@ JSC::JSValue createNodeURLBinding(Zig::GlobalObject* globalObject)
|
||||
ASSERT(domainToAsciiFunction);
|
||||
auto domainToUnicodeFunction = JSC::JSFunction::create(vm, globalObject, 1, "domainToUnicode"_s, jsDomainToUnicode, ImplementationVisibility::Public);
|
||||
ASSERT(domainToUnicodeFunction);
|
||||
binding->putDirectIndex(
|
||||
binding->putByIndexInline(
|
||||
globalObject,
|
||||
(unsigned)0,
|
||||
domainToAsciiFunction,
|
||||
0,
|
||||
JSC::PutDirectIndexMode::PutDirectIndexLikePutDirect);
|
||||
binding->putDirectIndex(
|
||||
false);
|
||||
binding->putByIndexInline(
|
||||
globalObject,
|
||||
(unsigned)1,
|
||||
domainToUnicodeFunction,
|
||||
0,
|
||||
JSC::PutDirectIndexMode::PutDirectIndexLikePutDirect);
|
||||
false);
|
||||
return binding;
|
||||
}
|
||||
|
||||
|
||||
@@ -92,55 +92,43 @@ const kDeferredTimeouts = Symbol("deferredTimeouts");
|
||||
|
||||
const kEmptyObject = Object.freeze(Object.create(null));
|
||||
|
||||
// These are declared as plain objects instead of `const enum` to prevent the
|
||||
// TypeScript enum reverse-mapping pattern (e.g. `Enum[Enum["x"] = 0] = "x"`)
|
||||
// from triggering setters on `Object.prototype` during module initialization.
|
||||
// See: https://github.com/oven-sh/bun/issues/24336
|
||||
export const ClientRequestEmitState = {
|
||||
socket: 1,
|
||||
prefinish: 2,
|
||||
finish: 3,
|
||||
response: 4,
|
||||
} as const;
|
||||
export type ClientRequestEmitState = (typeof ClientRequestEmitState)[keyof typeof ClientRequestEmitState];
|
||||
export const enum ClientRequestEmitState {
|
||||
socket = 1,
|
||||
prefinish = 2,
|
||||
finish = 3,
|
||||
response = 4,
|
||||
}
|
||||
|
||||
export const NodeHTTPResponseAbortEvent = {
|
||||
none: 0,
|
||||
abort: 1,
|
||||
timeout: 2,
|
||||
} as const;
|
||||
export type NodeHTTPResponseAbortEvent = (typeof NodeHTTPResponseAbortEvent)[keyof typeof NodeHTTPResponseAbortEvent];
|
||||
|
||||
export const NodeHTTPIncomingRequestType = {
|
||||
FetchRequest: 0,
|
||||
FetchResponse: 1,
|
||||
NodeHTTPResponse: 2,
|
||||
} as const;
|
||||
export type NodeHTTPIncomingRequestType =
|
||||
(typeof NodeHTTPIncomingRequestType)[keyof typeof NodeHTTPIncomingRequestType];
|
||||
|
||||
export const NodeHTTPBodyReadState = {
|
||||
none: 0,
|
||||
pending: 1 << 1,
|
||||
done: 1 << 2,
|
||||
hasBufferedDataDuringPause: 1 << 3,
|
||||
} as const;
|
||||
export type NodeHTTPBodyReadState = (typeof NodeHTTPBodyReadState)[keyof typeof NodeHTTPBodyReadState];
|
||||
export const enum NodeHTTPResponseAbortEvent {
|
||||
none = 0,
|
||||
abort = 1,
|
||||
timeout = 2,
|
||||
}
|
||||
export const enum NodeHTTPIncomingRequestType {
|
||||
FetchRequest,
|
||||
FetchResponse,
|
||||
NodeHTTPResponse,
|
||||
}
|
||||
export const enum NodeHTTPBodyReadState {
|
||||
none,
|
||||
pending = 1 << 1,
|
||||
done = 1 << 2,
|
||||
hasBufferedDataDuringPause = 1 << 3,
|
||||
}
|
||||
|
||||
// Must be kept in sync with NodeHTTPResponse.Flags
|
||||
export const NodeHTTPResponseFlags = {
|
||||
socket_closed: 1 << 0,
|
||||
request_has_completed: 1 << 1,
|
||||
closed_or_completed: (1 << 0) | (1 << 1),
|
||||
} as const;
|
||||
export type NodeHTTPResponseFlags = (typeof NodeHTTPResponseFlags)[keyof typeof NodeHTTPResponseFlags];
|
||||
export const enum NodeHTTPResponseFlags {
|
||||
socket_closed = 1 << 0,
|
||||
request_has_completed = 1 << 1,
|
||||
|
||||
export const NodeHTTPHeaderState = {
|
||||
none: 0,
|
||||
assigned: 1,
|
||||
sent: 2,
|
||||
} as const;
|
||||
export type NodeHTTPHeaderState = (typeof NodeHTTPHeaderState)[keyof typeof NodeHTTPHeaderState];
|
||||
closed_or_completed = socket_closed | request_has_completed,
|
||||
}
|
||||
|
||||
export const enum NodeHTTPHeaderState {
|
||||
none,
|
||||
assigned,
|
||||
sent,
|
||||
}
|
||||
|
||||
function emitErrorNextTickIfErrorListenerNT(self, err, cb) {
|
||||
process.nextTick(emitErrorNextTickIfErrorListener, self, err, cb);
|
||||
|
||||
@@ -4244,6 +4244,132 @@ pub const Resolver = struct {
|
||||
// todo deinit these parent configs somehow?
|
||||
}
|
||||
info.tsconfig_json = merged_config;
|
||||
|
||||
// Handle "references" - load each referenced tsconfig and merge
|
||||
// their paths into this config. This supports the common pattern
|
||||
// where a root tsconfig.json uses "references" to delegate to
|
||||
// sub-configs (e.g. tsconfig.app.json, tsconfig.node.json).
|
||||
if (merged_config.references.len > 0) {
|
||||
const ts_dir_name = Dirname.dirname(merged_config.abs_path);
|
||||
for (merged_config.references) |ref_path| {
|
||||
// Per the TypeScript spec, if "path" points to a directory,
|
||||
// look for tsconfig.json inside it. If it points to a file,
|
||||
// use it directly.
|
||||
const abs_ref_path = brk2: {
|
||||
if (strings.endsWithComptime(ref_path, ".json")) {
|
||||
break :brk2 ResolvePath.joinAbsStringBuf(
|
||||
ts_dir_name,
|
||||
bufs(.tsconfig_path_abs),
|
||||
&[_]string{ ts_dir_name, ref_path },
|
||||
.auto,
|
||||
);
|
||||
} else {
|
||||
break :brk2 ResolvePath.joinAbsStringBuf(
|
||||
ts_dir_name,
|
||||
bufs(.tsconfig_path_abs),
|
||||
&[_]string{ ts_dir_name, ref_path, "tsconfig.json" },
|
||||
.auto,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const ref_config_maybe = r.parseTSConfig(abs_ref_path, bun.invalid_fd) catch |err| brk2: {
|
||||
r.log.addDebugFmt(null, logger.Loc.Empty, r.allocator, "{s} loading tsconfig.json reference {f}", .{
|
||||
@errorName(err),
|
||||
bun.fmt.QuotedFormatter{
|
||||
.text = abs_ref_path,
|
||||
},
|
||||
}) catch {};
|
||||
break :brk2 null;
|
||||
};
|
||||
if (ref_config_maybe) |ref_config| {
|
||||
// Also resolve extends chains for referenced configs
|
||||
var ref_parent_configs = try bun.BoundedArray(*TSConfigJSON, 64).init(0);
|
||||
try ref_parent_configs.append(ref_config);
|
||||
var ref_current = ref_config;
|
||||
while (ref_current.extends.len > 0) {
|
||||
const ref_ts_dir = Dirname.dirname(ref_current.abs_path);
|
||||
const ref_extends_abs = ResolvePath.joinAbsStringBuf(ref_ts_dir, bufs(.tsconfig_path_abs), &[_]string{ ref_ts_dir, ref_current.extends }, .auto);
|
||||
const ref_parent_maybe = r.parseTSConfig(ref_extends_abs, bun.invalid_fd) catch break;
|
||||
if (ref_parent_maybe) |ref_parent| {
|
||||
try ref_parent_configs.append(ref_parent);
|
||||
ref_current = ref_parent;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Merge the referenced config's extends chain
|
||||
// (same fields as the root extends merge)
|
||||
var ref_merged = ref_parent_configs.pop().?;
|
||||
while (ref_parent_configs.pop()) |ref_parent| {
|
||||
ref_merged.emit_decorator_metadata = ref_merged.emit_decorator_metadata or ref_parent.emit_decorator_metadata;
|
||||
if (ref_parent.base_url.len > 0) {
|
||||
ref_merged.base_url = ref_parent.base_url;
|
||||
ref_merged.base_url_for_paths = ref_parent.base_url_for_paths;
|
||||
}
|
||||
ref_merged.jsx = ref_parent.mergeJSX(ref_merged.jsx);
|
||||
ref_merged.jsx_flags.setUnion(ref_parent.jsx_flags);
|
||||
|
||||
if (ref_parent.preserve_imports_not_used_as_values) |value| {
|
||||
ref_merged.preserve_imports_not_used_as_values = value;
|
||||
}
|
||||
|
||||
var ref_iter = ref_parent.paths.iterator();
|
||||
while (ref_iter.next()) |c| {
|
||||
ref_merged.paths.put(c.key_ptr.*, c.value_ptr.*) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// Merge referenced config's paths into the root config.
|
||||
// Path values need to be made absolute using the referenced
|
||||
// config's base_url_for_paths, since the root config may
|
||||
// have a different (or no) base URL.
|
||||
const ref_base = if (ref_merged.hasBaseURL()) ref_merged.base_url else ref_merged.base_url_for_paths;
|
||||
var ref_iter = ref_merged.paths.iterator();
|
||||
while (ref_iter.next()) |c| {
|
||||
const original_values = c.value_ptr.*;
|
||||
if (ref_base.len > 0 and (merged_config.base_url_for_paths.len == 0 or
|
||||
!strings.eql(ref_base, merged_config.base_url_for_paths)))
|
||||
{
|
||||
// Resolve each path value to absolute so it works
|
||||
// regardless of the root config's baseUrl
|
||||
var abs_values = bun.default_allocator.alloc(string, original_values.len) catch unreachable;
|
||||
for (original_values, 0..) |orig_path, i| {
|
||||
if (!std.fs.path.isAbsolute(orig_path)) {
|
||||
const join_parts = [_]string{ ref_base, orig_path };
|
||||
abs_values[i] = r.fs.dirname_store.append(
|
||||
string,
|
||||
r.fs.absBuf(&join_parts, bufs(.tsconfig_base_url)),
|
||||
) catch unreachable;
|
||||
} else {
|
||||
abs_values[i] = orig_path;
|
||||
}
|
||||
}
|
||||
merged_config.paths.put(c.key_ptr.*, abs_values) catch unreachable;
|
||||
} else {
|
||||
merged_config.paths.put(c.key_ptr.*, original_values) catch unreachable;
|
||||
}
|
||||
}
|
||||
|
||||
// If the root config has no base_url_for_paths but the referenced
|
||||
// config has paths, we need to ensure base_url_for_paths is set
|
||||
if (merged_config.base_url_for_paths.len == 0 and ref_merged.paths.count() > 0) {
|
||||
merged_config.base_url_for_paths = ref_merged.base_url_for_paths;
|
||||
}
|
||||
|
||||
// Merge other settings from referenced configs
|
||||
merged_config.jsx = ref_merged.mergeJSX(merged_config.jsx);
|
||||
merged_config.jsx_flags.setUnion(ref_merged.jsx_flags);
|
||||
merged_config.emit_decorator_metadata = merged_config.emit_decorator_metadata or ref_merged.emit_decorator_metadata;
|
||||
|
||||
if (ref_merged.preserve_imports_not_used_as_values) |value| {
|
||||
if (merged_config.preserve_imports_not_used_as_values == null) {
|
||||
merged_config.preserve_imports_not_used_as_values = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
info.enclosing_tsconfig_json = info.tsconfig_json;
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ pub const TSConfigJSON = struct {
|
||||
emit_decorator_metadata: bool = false,
|
||||
experimental_decorators: bool = false,
|
||||
|
||||
// TypeScript project references. Each entry is the "path" value from the
|
||||
// "references" array in tsconfig.json. These are relative paths to other
|
||||
// tsconfig files (or directories containing tsconfig.json).
|
||||
// See: https://www.typescriptlang.org/docs/handbook/project-references.html
|
||||
references: []const string = &.{},
|
||||
|
||||
pub fn hasBaseURL(tsconfig: *const TSConfigJSON) bool {
|
||||
return tsconfig.base_url.len > 0;
|
||||
}
|
||||
@@ -158,6 +164,26 @@ pub const TSConfigJSON = struct {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Parse "references"
|
||||
if (json.asProperty("references")) |references_value| {
|
||||
if (!source.path.isNodeModule()) {
|
||||
if (references_value.expr.asArray()) |ref_array_iter| {
|
||||
var ref_array = ref_array_iter;
|
||||
var refs = std.array_list.Managed(string).init(allocator);
|
||||
while (ref_array.next()) |element| {
|
||||
if (element.asProperty("path")) |path_prop| {
|
||||
if (path_prop.expr.asString(allocator)) |ref_path| {
|
||||
refs.append(ref_path) catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (refs.items.len > 0) {
|
||||
result.references = refs.toOwnedSlice() catch unreachable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var has_base_url = false;
|
||||
|
||||
// Parse "compilerOptions"
|
||||
|
||||
95
test/regression/issue/20172.test.ts
Normal file
95
test/regression/issue/20172.test.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunRun, tempDirWithFiles } from "harness";
|
||||
import { join } from "path";
|
||||
|
||||
test("tsconfig references resolves paths from referenced configs", () => {
|
||||
const dir = tempDirWithFiles("tsconfig-refs", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: "./tsconfig.app.json" }, { path: "./tsconfig.node.json" }],
|
||||
}),
|
||||
"tsconfig.app.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"@/*": ["./src/*"],
|
||||
},
|
||||
},
|
||||
include: ["src/**/*"],
|
||||
}),
|
||||
"tsconfig.node.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
paths: {
|
||||
"@server/*": ["./server/*"],
|
||||
},
|
||||
},
|
||||
include: ["server/**/*"],
|
||||
}),
|
||||
"server/index.ts": `import { foo } from '@server/lib/foo';
|
||||
console.log(foo);`,
|
||||
"server/lib/foo.ts": `export const foo = 123;`,
|
||||
"src/main.ts": `import { bar } from '@/lib/bar';
|
||||
console.log(bar);`,
|
||||
"src/lib/bar.ts": `export const bar = 456;`,
|
||||
});
|
||||
|
||||
// Test @server/* paths from tsconfig.node.json
|
||||
const serverResult = bunRun(join(dir, "server/index.ts"));
|
||||
expect(serverResult.stdout).toBe("123");
|
||||
|
||||
// Test @/* paths from tsconfig.app.json
|
||||
const appResult = bunRun(join(dir, "src/main.ts"));
|
||||
expect(appResult.stdout).toBe("456");
|
||||
});
|
||||
|
||||
test("tsconfig references resolves directory references", () => {
|
||||
const dir = tempDirWithFiles("tsconfig-dir-refs", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: "./app" }],
|
||||
}),
|
||||
"app/tsconfig.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: "..",
|
||||
paths: {
|
||||
"#utils/*": ["./src/utils/*"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"src/index.ts": `import { helper } from '#utils/helper';
|
||||
console.log(helper);`,
|
||||
"src/utils/helper.ts": `export const helper = "works";`,
|
||||
});
|
||||
|
||||
const result = bunRun(join(dir, "src/index.ts"));
|
||||
expect(result.stdout).toBe("works");
|
||||
});
|
||||
|
||||
test("tsconfig references with extends in referenced config", () => {
|
||||
const dir = tempDirWithFiles("tsconfig-refs-extends", {
|
||||
"tsconfig.json": JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: "./tsconfig.app.json" }],
|
||||
}),
|
||||
"tsconfig.app.json": JSON.stringify({
|
||||
extends: "./tsconfig.base.json",
|
||||
compilerOptions: {
|
||||
paths: {
|
||||
"@app/*": ["./src/*"],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"tsconfig.base.json": JSON.stringify({
|
||||
compilerOptions: {
|
||||
baseUrl: ".",
|
||||
},
|
||||
}),
|
||||
"src/index.ts": `import { val } from '@app/lib/val';
|
||||
console.log(val);`,
|
||||
"src/lib/val.ts": `export const val = "extended";`,
|
||||
});
|
||||
|
||||
const result = bunRun(join(dir, "src/index.ts"));
|
||||
expect(result.stdout).toBe("extended");
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe } from "harness";
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/24336
|
||||
// require('http') should not trigger Object.prototype setters during module loading.
|
||||
// Node.js produces no output for both CJS and ESM, and Bun should match that behavior.
|
||||
test("require('http') does not trigger Object.prototype[0] setter", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
Object.defineProperty(Object.prototype, '0', {
|
||||
set() { console.log('SETTER_TRIGGERED'); }
|
||||
});
|
||||
require('http');
|
||||
`,
|
||||
],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toBe("");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("require('url') does not trigger Object.prototype[0] setter", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
Object.defineProperty(Object.prototype, '0', {
|
||||
set() { console.log('SETTER_TRIGGERED'); }
|
||||
});
|
||||
require('url');
|
||||
`,
|
||||
],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toBe("");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
|
||||
test("require('util') does not trigger Object.prototype[0] setter", async () => {
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [
|
||||
bunExe(),
|
||||
"-e",
|
||||
`
|
||||
Object.defineProperty(Object.prototype, '0', {
|
||||
set() { console.log('SETTER_TRIGGERED'); }
|
||||
});
|
||||
require('util');
|
||||
`,
|
||||
],
|
||||
env: bunEnv,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stdout).toBe("");
|
||||
expect(stderr).toBe("");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user