Skip to content
Merged
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions packages/bun-error/runtime-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m;
const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/;

export default class RuntimeError {
original: Error;
stack: StackFrame[];
original!: Error;
stack!: StackFrame[];

static from(error: Error): RuntimeError {
const runtime = new RuntimeError();
Expand Down Expand Up @@ -75,7 +75,7 @@ export default class RuntimeError {

var regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/;
var parts = regExp.exec(urlLike.replace(/[()]/g, ""));
return [parts[1], parts[2] || undefined, parts[3] || undefined];
return [parts![1], parts![2] || undefined, parts![3] || undefined];
}

static parseV8OrIE(error) {
Expand Down
8 changes: 4 additions & 4 deletions packages/bun-release/src/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export function rm(path: string): void {
return;
}
try {
fs.rmdirSync(path, { recursive: true });
(fs.rmdirSync as typeof fs.rmSync)(path, { recursive: true });
return;
} catch (error) {
debug("fs.rmdirSync failed", error);
Expand Down Expand Up @@ -76,7 +76,7 @@ export function rename(path: string, newPath: string): void {
export function write(dst: string, content: string | ArrayBuffer | ArrayBufferView): void {
debug("write", dst);
try {
fs.writeFileSync(dst, content);
fs.writeFileSync(dst, content as string | NodeJS.ArrayBufferView);
return;
} catch (error) {
debug("fs.writeFileSync failed", error);
Expand All @@ -88,7 +88,7 @@ export function write(dst: string, content: string | ArrayBuffer | ArrayBufferVi
debug("fs.mkdirSync failed", error);
// The directory could have been created already.
}
fs.writeFileSync(dst, content);
fs.writeFileSync(dst, content as string | NodeJS.ArrayBufferView);
}
}

Expand Down Expand Up @@ -124,7 +124,7 @@ export function blob(path: string): Blob {
});
}

export function hash(content: string | crypto.BinaryLike): string {
export function hash(content: string | NodeJS.ArrayBufferView): string {
debug("hash", content);
return crypto
.createHash("sha256")
Expand Down
6 changes: 3 additions & 3 deletions packages/bun-release/src/platform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,15 @@ export const supportedPlatforms: Platform[] = getSupportedPlatforms(os, arch, ab
function isLinuxMusl(): boolean {
try {
if (process.report) {
const excludeNetwork = process.report.excludeNetwork;
process.report.excludeNetwork = true;
const excludeNetwork = (process.report as any).excludeNetwork;
(process.report as any).excludeNetwork = true;
try {
const report = process.report.getReport() as { header?: { glibcVersionRuntime?: string } };
if (report && report.header) {
return !report.header.glibcVersionRuntime;
}
} finally {
process.report.excludeNetwork = excludeNetwork;
(process.report as any).excludeNetwork = excludeNetwork;
}
}
} catch (error) {
Expand Down
23 changes: 14 additions & 9 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -571,9 +571,6 @@ declare module "bun" {
cancel?: UnderlyingSourceCancelCallback;
pull?: UnderlyingSourcePullCallback<R>;
start?: UnderlyingSourceStartCallback<R>;
/**
* Mode "bytes" is not supported.
*/
type?: undefined;
}

Expand Down Expand Up @@ -1084,7 +1081,8 @@ declare module "bun" {
*
* @category Utilities
*
* @param input The TOML document to parse, as a string or UTF-8 bytes
* @param input The TOML document to parse, as a string or UTF-8 bytes. A `Blob` that is backed by a file, such
* as `Bun.file()`, throws: pass `await file.text()`.
* @returns A JavaScript object
* @throws {SyntaxError} If the input is not valid TOML
*/
Expand Down Expand Up @@ -1123,7 +1121,11 @@ declare module "bun" {
* // 'name = "app"\n\n[server]\nport = 8080\n'
* ```
*/
export function stringify(input: unknown, replacer?: undefined | null, space?: string | number): string | undefined;
export function stringify<T>(
input: T,
replacer?: undefined | null,
space?: string | number,
): T extends Function | symbol | undefined ? undefined : T extends object ? string : string | undefined;
}

/**
Expand Down Expand Up @@ -1496,7 +1498,8 @@ declare module "bun" {
*
* @category Utilities
*
* @param input The YAML string to parse
* @param input The YAML document to parse, as a string or UTF-8 bytes. A `Blob` that is backed by a file, such
* as `Bun.file()`, throws: pass `await file.text()`.
* @returns A JavaScript value, or an array of them for a multi-document stream
*
* @example
Expand All @@ -1511,7 +1514,9 @@ declare module "bun" {
* console.log(YAML.parse("abc: def")) // { "abc": "def" }
* ```
*/
export function parse(input: string): unknown;
export function parse(
input: string | NodeJS.TypedArray | DataView<ArrayBufferLike> | ArrayBufferLike | Blob,
Comment thread
claude[bot] marked this conversation as resolved.
): unknown;

/**
* Convert a JavaScript value into a YAML string. Strings are double quoted if they contain keywords, non-printable or
Expand Down Expand Up @@ -10061,9 +10066,9 @@ declare module "bun" {
resize(width: number, height: number): Promise<void>;

/** Navigate back in session history. */
back(): Promise<void>;
goBack(): Promise<void>;
/** Navigate forward in session history. */
forward(): Promise<void>;
goForward(): Promise<void>;
/** Reload the current page. */
reload(): Promise<void>;

Expand Down
19 changes: 18 additions & 1 deletion packages/bun-types/globals.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ declare var ReadableStream: Bun.__internal.UseLibDomIfAvailable<
prototype: ReadableStream;
new <R = any>(underlyingSource?: Bun.UnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;
new <R = any>(underlyingSource?: Bun.DirectUnderlyingSource<R>, strategy?: QueuingStrategy<R>): ReadableStream<R>;
new <R extends Uint8Array = Uint8Array<ArrayBuffer>>(
underlyingSource: import("node:stream/web").UnderlyingByteSource,
strategy?: { highWaterMark?: number },
): ReadableStream<R>;
}
>;

Expand Down Expand Up @@ -1502,6 +1506,16 @@ interface Blob {
*/
readonly type: string;

/**
* Returns a new `Blob` with the bytes from `start` up to but not including
* `end`. Negative indices count from the end.
*
* @param start byte offset to start at. Defaults to 0.
* @param end byte offset to stop at. Defaults to `size`.
* @param contentType the `type` of the new `Blob`.
*/
slice(start?: number, end?: number, contentType?: string): Blob;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 nit (optional): Users without lib.dom who call new Blob([...]).slice("text/plain") or .slice(1, "text/plain") get a type error after merging, although the runtime accepts both. The new Blob.slice declaration at packages/bun-types/globals.d.ts:1517 lists only (start?, end?, contentType?), while the shared runtime implementation also takes a string first or second argument. Fix: give the new Blob.slice the same three overloads BunFile.slice already declares at packages/bun-types/bun.d.ts:2696-2715, so a plain Blob no longer needs a BunFile cast to type-check. The PR hides the gap at test/js/web/fetch/blob.test.ts:66 with (blob as Bun.BunFile) on a plain new Blob(["Bun", "Foo"]).

Why this was flagged

A bun-types consumer without lib.dom writes new Blob(["a"]).slice("text/plain") or .slice(1, "text/plain"). packages/bun-types/globals.d.ts:1517 is the only slice on interface Blob, typed slice(start?: number, end?: number, contentType?: string): Blob, so a string in the first or second position is TS2345. At runtime get_slice in src/runtime/webcore/Blob.rs:1844-1851 moves a string found in args[0] or args[1] into the contentType slot for every Blob, and test/js/web/fetch/blob.test.ts:26-66 asserts blob.slice("text/plain;charset=utf-8").type for a plain new Blob(["Bun", "Foo"]). The PR makes that line type-check by casting the plain Blob to Bun.BunFile (blob.test.ts:66), whose declaration at packages/bun-types/bun.d.ts:2696-2715 has the three overloads the runtime implements. On the base branch slice was not declared on Blob at all without lib.dom, so the declaration this PR adds is new surface that is narrower than the runtime it documents.

Verification: nit — triggers when a bun-types consumer without lib.dom calls Blob#slice with a string in the first or second position. The diff adds exactly one slice to interface Blob at packages/bun-types/globals.d.ts:1517. The runtime in src/runtime/webcore/Blob.rs:1844-1851 explicitly shifts a string in position 0 or 1 into the contentType slot for every Blob. Nothing that compiled on the base breaks.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried it, and it breaks more than it fixes. With slice(start?: number, contentType?: string) and slice(contentType?: string) added to the global Blob, 9 of the 22 cases in bun-types.test.ts fail, starting with the fixture for #24154:

24154.ts:5:3  TS2322: Type 'import("node:buffer").Blob' is not assignable to type 'Blob'.
  Types of property 'slice' are incompatible.
    Types of parameters 'end' and 'contentType' are incompatible.
      Type 'string' is not assignable to type 'number'.

That is async function example(): Promise<Blob> { return await (await fetch("foo")).blob(); }: response.blob() is the Blob of node:buffer, whose slice has the one standard signature, so it stops being assignable to a global Blob that has more. #41809 came to the same conclusion. The one call in blob.test.ts that uses the string-first form keeps its cast.


/**
* Read the data from the blob as a JSON object.
*
Expand Down Expand Up @@ -1822,7 +1836,10 @@ declare var ReadableStreamBYOBRequest: Bun.__internal.UseLibDomIfAvailable<
interface TextDecoderStream extends Bun.__internal.LibEmptyOrNodeStreamWebTextDecoderStream {}
declare var TextDecoderStream: Bun.__internal.UseLibDomIfAvailable<
"TextDecoderStream",
{ prototype: TextDecoderStream; new (): TextDecoderStream }
{
prototype: TextDecoderStream;
new (encoding?: Bun.Encoding, options?: { fatal?: boolean; ignoreBOM?: boolean }): TextDecoderStream;
}
>;

interface TextEncoderStream extends Bun.__internal.LibEmptyOrNodeStreamWebTextEncoderStream {}
Expand Down
37 changes: 28 additions & 9 deletions packages/bun-types/test.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,11 +239,11 @@ declare module "bun:test" {
/**
* Skips this group of tests.
*/
skip: Describe<T>;
skip: ((label: string | number) => void) & Describe<T>;
/**
* Marks this group of tests as to be written or to be fixed.
*/
todo: Describe<T>;
todo: ((label: string | number) => void) & Describe<T>;
/**
* Marks this group of tests to be executed concurrently.
*/
Expand Down Expand Up @@ -279,8 +279,8 @@ declare module "bun:test" {
* @param table Array of Arrays with the arguments that are passed into the test fn for each row.
*/
each<T extends Readonly<[any, ...any[]]>>(table: readonly T[]): Describe<[...T]>;
each<T extends any[]>(table: readonly T[]): Describe<[...T]>;
each<const T>(table: T[]): Describe<[T]>;
each<T extends readonly any[]>(table: readonly T[]): Describe<[...T]>;
each<const T>(table: readonly T[]): Describe<[T]>;
}
/**
* Describes a group of related tests.
Expand Down Expand Up @@ -477,6 +477,25 @@ declare module "bun:test" {
* @category Testing
*/
export interface Test<T extends ReadonlyArray<unknown>> {
/**
* Runs a test, with the options before the test function.
*
* @example
* ```ts
* test("can retry", { retry: 3 }, () => {
* expect(Math.random()).toBeLessThan(0.5);
* });
* ```
*/
(
label: string,
options: number | TestOptions,
fn: (
...args: __internal.IsTuple<T> extends true
? [...table: __internal.Flatten<T>, done: (err?: unknown) => void]
: T
) => void | Promise<unknown>,
): void;
(
label: string,

Expand All @@ -502,15 +521,15 @@ declare module "bun:test" {
/**
* Skips this test.
*/
skip: Test<T>;
skip: ((label: string) => void) & Test<T>;
/**
* Marks this test as to be written or to be fixed.
*
* These tests only run when the `--todo` flag is passed. With the flag,
* a `.todo` test that passes is marked as `fail` in the results: remove
* the `.todo` or check that the test is implemented correctly.
*/
todo: Test<T>;
todo: ((label: string) => void) & Test<T>;
/**
* Marks this test as failing.
*
Expand Down Expand Up @@ -575,8 +594,8 @@ declare module "bun:test" {
* @param table Array of Arrays with the arguments that are passed into the test fn for each row.
*/
each<T extends Readonly<[unknown, ...unknown[]]>>(table: readonly T[]): Test<T>;
each<T extends unknown[]>(table: readonly T[]): Test<T>;
each<const T>(table: T[]): Test<[T]>;
each<T extends readonly unknown[]>(table: readonly T[]): Test<T>;
each<const T>(table: readonly T[]): Test<[T]>;
}
/**
* Runs a test.
Expand Down Expand Up @@ -633,7 +652,7 @@ declare module "bun:test" {
* @param customFailMessage an optional custom message to display if the test fails.
* */

(actual?: never, customFailMessage?: string): Matchers<undefined>;
(): Matchers<undefined>;
Comment thread
claude[bot] marked this conversation as resolved.
<T = unknown>(actual: T, customFailMessage?: string): Matchers<T>;
<T = unknown>(actual?: T, customFailMessage?: string): Matchers<T | undefined>;

Expand Down
2 changes: 1 addition & 1 deletion scripts/build/jsonByteClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export function generateJsonByteClass(cfg: Pick<Config, "codegenDir">): { h: str

const table: string[] = [];
for (let row = 0; row < 256; row += 16) {
const cells = [];
const cells: string[] = [];
for (let b = row; b < row + 16; b++) cells.push(hex(classOf(b)));
table.push(` ${cells.join(", ")},`);
}
Expand Down
2 changes: 1 addition & 1 deletion scripts/build/xmlByteClass.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export function generateXmlByteClass(cfg: Pick<Config, "codegenDir">): { h: stri

const table: string[] = [];
for (let row = 0; row < 256; row += 16) {
const cells = [];
const cells: string[] = [];
for (let b = row; b < row + 16; b++) cells.push(hex(classOf(b)));
table.push(` ${cells.join(", ")},`);
}
Expand Down
26 changes: 26 additions & 0 deletions test/asset-imports.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Files that tests import for their path (the "file" loader) or their text (`with { type: "text" }`).
// TypeScript 7.1 can declare these by import attribute instead of by extension, see packages/bun-types/ts7.1.
declare module "*.c" {
var contents: string;
export = contents;
}
declare module "*.cc" {
var contents: string;
export = contents;
}
declare module "*.h" {
var contents: string;
export = contents;
}
declare module "*.pem" {
var contents: string;
export = contents;
}
declare module "*.br" {
var contents: string;
export = contents;
}
declare module "*.gzip" {
var contents: string;
export = contents;
}
3 changes: 1 addition & 2 deletions test/bake/bake-harness.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// <reference path="../../src/bake/bake.d.ts" />
/// <reference path="../../src/runtime/bake/bake.d.ts" />
/* Dev server tests can be run with `bun test` or in interactive mode with `bun run test.ts "name filter"`
*
* Env vars:
Expand Down Expand Up @@ -1731,7 +1731,6 @@ async function writeAll(root: string, files: FileObject) {
fs.mkdirSync(path.dirname(filename), { recursive: true });
const formattedContents =
typeof contents === "string" ? dedent(contents).replaceAll("{{root}}", root.replaceAll("\\", "\\\\")) : contents;
// @ts-expect-error the type of Bun.write is too strict
promises.push(Bun.write(filename, formattedContents));
}
await Promise.all(promises);
Expand Down
Loading
Loading