Skip to content

test: load bun-types once in test/, and fix the type errors since #44614 - #45934

Draft
robobun wants to merge 5 commits into
mainfrom
robobun/a9200230/test-type-errors-again
Draft

robobun wants to merge 5 commits into
mainfrom
robobun/a9200230/test-type-errors-again

Conversation

@robobun

@robobun robobun commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator

Behaviour change: none

Problem

Fix

  • test/tsconfig.json maps "bun" to packages/bun-types/index.d.ts in paths, so the copy is never loaded.
  • 96 files get type-only edits, seven of them // @ts-expect-error. bun build --no-bundle prints the same bytes for each file as on main.
  • Verified: bun check and tsc 7.0.2 report no errors. 101 of 102 touched test files give the same results on both trees.
  • Self-reviewed: 22 concerns raised, 22 answered, three in the rebase. test: fix five type errors that landed beside #44614 #44861 stays open for a maintainer to close.

Background

  • Two copies of a declaration merge. Each method of an interface in declare module "bun" gets two signatures, and with noImplicitAny off its callbacks get any parameters.
  • "bun" is a builtin: the runtime and the bundler resolve it before they read paths.
  • Considered deleting the 12 unused // @ts-expect-error and casting 13 calls: callbacks stay untyped.

Downsides

  • Without a CI step the zero does not last: 24 of the 127 commits since Fix every type error in test/ #44614 left a type error. A maintainer decides: a lint.yml step that runs bun check in test/, or a one-time sweep. The rebase waits on robobun/a9200230/test-type-errors-rebased for the answer.
  • Seven new Bun.Archive(files).bytes("gzip") calls stay. bytes() takes no argument, so those tests serve an uncompressed tarball.
Notes

State on 2026-10-19, and what waits for the rebase

  • Main is at 91e7e3b and reports Found 369 errors in 101 files, checked 2,866 files. With only the paths entry it reports 348 in 96 files.
  • This branch conflicts with main in five files, all from install: fix regressions since 1.4.3 and resolve peer dependencies more like pnpm #45898: bun-lockb.test.ts, bun-pack.test.ts, bun-workspaces.test.ts, hoist.test.ts and run-autoinstall.test.ts.
  • The branch robobun/a9200230/test-type-errors-rebased (d4969ce) is this PR on 91e7e3b, with the six conflict regions redone on main's text and the five errors that install: fix regressions since 1.4.3 and resolve peer dependencies more like pnpm #45898 and bun check: 1.4x to 7.8x faster on large projects. Fixes for bun check, bun lint and bun fmt #45800 added fixed. There, bun check prints No type errors in 2,866 files, tsc 7.0.2 agrees, the 96 files transpile to the same JavaScript as on main, and the formatter changes nothing.
  • It is not pushed here, because each push is a full CI run and main got five more type errors in the last day. I push it once, on a maintainer's go-ahead.
  • The rebased branch has three changes from the self-review. Bun.sha(file as any) replaces the directive in bun-cryptohasher.test.ts: the declaration is the gap there, not the test, so six // @ts-expect-error are left, all on bytes("gzip"). The second isDirectoryTree in harness.ts takes a Buffer again, because that hunk fixed no error. A comment marks the seventh bytes("gzip") call, which is inside a -e string at bun-pm-diff.test.ts:1662 and so has no type error.

The CI step, if a maintainer wants it

  • In the lint-js job of .github/workflows/lint.yml: bun install --frozen-lockfile --cwd test, then cd test && bun check. .github/workflows/bun-types.yml already installs test/ in a GitHub job.
  • The price: git blame of the 325 lines that have an error on main today says that 24 of the 127 commits since Fix every type error in test/ #44614 last changed one of them. Each of those PRs would have had to fix its own errors before it merged.
  • The step must not use the root's TypeScript 6.0.2 (bun x tsc, as the other steps of lint.yml do). It reports 2 more errors, the TS2786 in jsx-dev.tsx and jsx-production.tsx that Fix every type error in test/ #44614 describes. cd test && bun check, equal to tsc 7.0.2, is the check this PR brings to zero.
  • As Fix every type error in test/ #44614 says, bun check at the repository root still stops at baseUrl in the three tsconfig files below src/.
  • If only the durable part is wanted, it is the paths entry. Alone it takes main from 369 to 348 errors, and four of the 348 are the ones the copy hid.

Type checks, in test/, after bun install --frozen-lockfile in the root and in test/

Checker main 5900354 main with only the paths entry this branch
bun check, release build of 5900354 Found 364 errors in 101 files not run No type errors in 2,865 files
bun check, canary 1.4.4-canary.1+ed680823c the same 364 Found 343 errors in 96 files No type errors in 2,865 files
tsc 7.0.2 (typescript7 of test/package.json) the same 364 not run no errors
tsc 6.0.2 (the root's typescript) not run not run 2, the TS2786 in jsx-dev.tsx and jsx-production.tsx that #44614 describes

The second copy of bun-types

  • test/package.json depends on bun-plugin-svelte as file:../packages/bun-plugin-svelte. That package has the devDependency "@types/bun": "../bun-types".
  • install: consolidate the open package manager pull requests #44968 regenerated test/bun.lock. Since then the lockfile has "bun-plugin-svelte/@types/bun": ["@types/bun@file:../packages/bun-types", {}], and bun install puts a copy of packages/bun-types in test/node_modules/.bun/@types+bun@file+..+packages+bun-types/. Before, the entry was bun-types@1.2.4-canary, which import "bun" does not find.
  • test/integration/svelte/*.ts import bun-plugin-svelte. Its src/index.ts imports "bun", and TypeScript resolves that to the copy next to it. tsc --listFilesOnly on main lists 29 files of packages/bun-types and 28 files of the copy. On this branch it lists 29 and 0.
  • With the paths entry alone, main goes from 364 to 343 errors: 25 go away, 4 appear.
  • The 25 that only exist with two copies: 12 unused // @ts-expect-error in websocket-server.test.ts (they are needed again now), 7 TS2349 in s3.test.ts, and 6 TS2769 in pipeTo-signal-leak.test.ts, fetch-abort-buffered-body-fixture.ts, module-graph-isolation.test.ts and regression/issue/26377.test.ts.
  • The 4 that the copy hid are in websocket-server.test.ts (lines 2741, 3558, 3747 and 3748 on main): ws.data of a server whose data type was not declared.
  • The paths entry does not change what runs. A probe file below test/ that imports, requires and resolves "bun" prints the same result with and without the entry, and bun build of it gives the same output for --target=bun, browser and node.
  • Also considered: exclude integration/svelte from the project. The next package that imports "bun" and has its own @types/bun brings a copy back.

Where the other errors come from

git blame of the 320 lines with an error (one copy): 316 were last changed after #44614. #44968 has 113 of them, #45307 41, #45304 40, #44805 37, #45223 34, #45043 12, #45146 10. The other 4 lines are the ones that #44861 covers. This PR has the same edits for them, so #44861 is not needed after it.

Test runs

Release build of 5900354, the environment of scripts/runner.node.ts, --timeout 30000. Main is a second worktree with the same node_modules. The 102 files are the 88 changed test files and the 14 that run a changed fixture or helper.

  • First run, both trees at once, two files at a time on each: 95 files have the same exit code, the same pass, fail, skip and todo counts and the same failing tests. 16,981 tests pass on main and 16,996 on this branch. 12 of the 95 fail on both trees in the same way. Most of those are js/sql tests that need a database this machine does not have.
  • The 7 that differed went both ways (4 with one more failure here, 3 with more failures on main). Every failure was a timeout or a timing assertion.
  • Run again with nothing else on the machine: bake.test.ts, check.test.ts, bun-install-git-deps.test.ts, bun-install-lifecycle-scripts.test.ts, bun-pack.test.ts and js-plugins.test.ts pass in full on both trees.
  • format.test.ts fails one or two tests in each of three runs on each tree, a different one each time: takes time in proportion to the size of the text and others that measure time.
  • bun-pack.test.ts failed 4 to 9 tests on main while that worktree was below /tmp. With the worktree beside the repository it passes.
  • Nothing ran on macOS or Windows.

Same JavaScript

For each of the 96 changed .ts files: bun build --no-bundle --target=bun <file> in a worktree of main and on this branch, the file in its place so that test/tsconfig.json applies. The output is byte for byte the same for all 96, with the release build of 5900354 and with the canary.

No hunk is unnecessary

For each hunk: revert it alone, run bun check, keep the revert if there is still no error. One hunk was dropped that way. Every other hunk brings an error back. On this branch harness.ts was left out of that loop. On the rebased branch it is in the loop, and the one hunk there that fixed no error is gone.

The two checks, as scripts

same-js.sh and minimize.py
#!/bin/bash
# usage: same-js.sh <path relative to the repository root> [...]
# HEAD_ROOT is this branch, BASE_ROOT a worktree of the base with the same node_modules.
BUN=${BUN:-bun}; HEAD_ROOT=${HEAD_ROOT:-$PWD}; BASE_ROOT=${BASE_ROOT:?a worktree of the base}
rc=0
# The transpiler writes __dirname and __filename as absolute paths, so the root of each tree is replaced.
t() { (cd "$1" && $BUN build --no-bundle --target=bun "$2" 2>/dev/null; echo "exit=$?") | sed "s#$1#ROOT#g"; }
for f in "$@"; do
  a=$(t "$HEAD_ROOT" "$f"); b=$(t "$BASE_ROOT" "$f")
  case "$a$b" in *exit=[1-9]*) echo "ERROR $f"; rc=1; continue;; esac
  if [ "$a" = "$b" ]; then echo "SAME $f"; else echo "DIFF $f"; rc=1; fi
done
exit $rc
#!/usr/bin/env python3
# For each hunk of each changed file: revert it alone and run the type check. A hunk whose revert leaves
# no error is unnecessary. One hunk per file per round, so a round costs one check.
import subprocess, re, os
ROOT = os.getcwd(); BUN = os.environ.get("BUN", "bun"); BASE = os.environ.get("BASE", "origin/main")
SKIP = {"test/tsconfig.json"}  # one hunk. Without it the check reports 25 errors.
def sh(*a): return subprocess.run(a, capture_output=True, text=True, cwd=ROOT)
def changed(): return [f for f in sh("git", "diff", "--name-only", BASE).stdout.split() if f not in SKIP]
def hunks(f):
    header, *hs = re.split(r"(?m)^(?=@@ )", sh("git", "diff", "-U0", BASE, "--", f).stdout)
    return header, [("\n".join(l for l in h.split("\n")[1:] if l and l[0] != "\\"), h) for h in hs]
def check():
    r = subprocess.run([BUN, "check", "--no-pretty", "--all"], capture_output=True, text=True, cwd=ROOT + "/test")
    out = r.stdout + r.stderr
    return set(re.findall(r"(?m)^([^\s(][^(\n]*)\(\d+,\d+\): error TS", out)), out
assert not check()[0]
tested, unnecessary = {}, []
while True:
    pending = {}
    for f in changed():
        header, hs = hunks(f); seen = {}
        for body, h in hs:
            seen[body] = seen.get(body, 0) + 1
            if seen[body] <= tested.setdefault(f, {}).get(body, 0): continue
            snapshot = open(f, "rb").read()
            open("/tmp/hunk.patch", "w").write(header + h)
            if sh("git", "apply", "-R", "--unidiff-zero", "/tmp/hunk.patch").returncode == 0:
                pending[f] = (body, snapshot); break
            tested[f][body] = tested[f].get(body, 0) + 1
    if not pending: break
    files, out = check()
    while "Stopped before type checking" in out:  # a syntax error: restore those files, check again
        for f in [f for f in pending if f[len("test/"):] in files]:
            body, snapshot = pending.pop(f); open(f, "wb").write(snapshot)
            tested[f][body] = tested[f].get(body, 0) + 1
        files, out = check()
    stray = [x for x in files if "test/" + x not in pending]  # a helper's hunk breaks other files
    for f, (body, snapshot) in pending.items():
        if f[len("test/"):] in files or stray:
            open(f, "wb").write(snapshot); tested[f][body] = tested[f].get(body, 0) + 1
        else:
            unnecessary.append((f, body))
print(unnecessary)

The bytes("gzip") calls

bun-pm-diff.test.ts (3), npmrc.test.ts (1) and run-autoinstall.test.ts (2) call new Bun.Archive(files).bytes("gzip") and serve the result as a .tgz. A seventh call is inside a -e string in bun-pm-diff.test.ts, where the checker does not see it. Archive#bytes() takes no argument (Archive.classes.ts has length: 0). With the release build of main, .bytes("gzip") returns the same 10,240 bytes as .bytes(), a plain tar. new Bun.Archive(files, { compress: "gzip" }).bytes() returns 146 bytes that start with 1f 8b. The tests pass because bun install also reads a plain tar. The fix would be the constructor option. It changes what the tests serve, so it is not in a PR of type-only edits.

The other // @ts-expect-error

  • bun-cryptohasher.test.ts: Bun.sha(file) with a file blob. Bun.sha() is declared to take Bun.StringOrBuffer, and the four calls above it take the same blob without an error. So the gap is in the declaration, and the rebased branch has a cast there.
  • internal/bindgen.test.ts: one directive was unused on main and is deleted.

Gaps in the declarations that the casts stand for

  • Response#clone() returns undici's Response, and getReader() returns the reader of node:stream/web. Fix every type error in test/ #44614 lists both.
  • @types/node: Channel#unsubscribe() is typed void and returns a boolean. OutgoingMessage#getRawHeaderNames() and allowHalfOpen for tls.connect() are not declared. inflateRawSync(..., { info: true }) is typed as a buffer.
  • $.braces() takes an undeclared second argument. undici 5's WebSocket type extends the type of the EventTarget constructor, so it has no dispatchEvent.
  • test.each types done as the last argument of every row. A callback with a rest parameter or a default value then gets string | done for each argument.

@robobun

robobun commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 1:11 PM PT - Oct 18th, 2026

✅ @robobun, your commit 4114d1b13fb43f8357366de5c049d1545039b61b passed in Build #128411! 🎉


🧪   To try this PR locally:

bunx bun-pr 45934

That installs a local version of the PR into your bun-45934 executable, so you can run:

bun-45934 --bun

@robobun

robobun commented Oct 18, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: draft. It waits for a maintainer's answer on the CI step (the question is in a comment below). This branch conflicts with main in five files, and the rebase is ready on robobun/a9200230/test-type-errors-rebased.

Reproduction

  1. On main 5900354, run bun install --frozen-lockfile in the repository root and in test/.
  2. Run cd test && bun check with a build of that commit (the canary has the command). ./node_modules/typescript7/bin/tsc --noEmit in test/ gives the same result.
  3. On main it prints Found 364 errors in 101 files, checked 2,865 files. On main 91e7e3b it prints 369.
  4. On this branch it prints No type errors in 2,865 files. On the rebased branch it prints No type errors in 2,866 files.

PR: #45934

@robobun

robobun commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

@Jarred-Sumner, one decision for you, because bun check and #44614 are yours.

Should .github/workflows/lint.yml get a step that type checks test/? It would run bun install --frozen-lockfile --cwd test, then cd test && bun check.

  • The price: 24 of the 127 commits that merged since Fix every type error in test/ #44614 left a type error that is still on main. Each of those PRs would have had to fix its own errors first.
  • Yes: I add the step to this PR, so the sweep and the step land together.
  • No: this PR is a one-time sweep, and test/ collects errors again. Main is at 369 today, 5 more than yesterday.

This branch conflicts with main in five files now. The rebased tree is ready on robobun/a9200230/test-type-errors-rebased: no type errors on 91e7e3b, and the same JavaScript for every changed file. I push it here once, on your answer, because each push is a full CI run.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant