From fd69af7356aa0ccb6f9dcb80cae078a55a4307e4 Mon Sep 17 00:00:00 2001 From: Alistair Smith Date: Tue, 26 Aug 2025 15:15:48 -0700 Subject: [PATCH] Avoid global React namespace in experimental.d.ts Co-authored-by: Parbez --- docs/guides/test/migrate-from-jest.md | 2 +- packages/bun-types/experimental.d.ts | 4 +- packages/bun-types/test-globals.d.ts | 2 +- test/integration/bun-types/bun-types.test.ts | 149 +++++++++++++++---- 4 files changed, 128 insertions(+), 29 deletions(-) diff --git a/docs/guides/test/migrate-from-jest.md b/docs/guides/test/migrate-from-jest.md index 80f2026450..59ccebc647 100644 --- a/docs/guides/test/migrate-from-jest.md +++ b/docs/guides/test/migrate-from-jest.md @@ -35,7 +35,7 @@ Add this directive to _just one file_ in your project, such as: - Any single `.ts` file that TypeScript includes in your compilation ```ts -/// +/// ``` --- diff --git a/packages/bun-types/experimental.d.ts b/packages/bun-types/experimental.d.ts index 093f00e622..cde43a2bff 100644 --- a/packages/bun-types/experimental.d.ts +++ b/packages/bun-types/experimental.d.ts @@ -191,7 +191,9 @@ declare module "bun" { * }; * ``` */ - export type SSGPage = React.ComponentType>; + export type SSGPage = import("react").ComponentType< + SSGPageProps + >; /** * getStaticPaths is Bun's implementation of SSG (Static Site Generation) path determination. diff --git a/packages/bun-types/test-globals.d.ts b/packages/bun-types/test-globals.d.ts index bd22f76131..bfc6e4f69c 100644 --- a/packages/bun-types/test-globals.d.ts +++ b/packages/bun-types/test-globals.d.ts @@ -3,7 +3,7 @@ // This file gets loaded by developers including the following triple slash directive: // // ```ts -// /// +// /// // ``` declare var test: typeof import("bun:test").test; diff --git a/test/integration/bun-types/bun-types.test.ts b/test/integration/bun-types/bun-types.test.ts index eecd8eb03b..05443dfe41 100644 --- a/test/integration/bun-types/bun-types.test.ts +++ b/test/integration/bun-types/bun-types.test.ts @@ -1,7 +1,8 @@ import { fileURLToPath, $ as Shell } from "bun"; import { afterAll, beforeAll, describe, expect, test } from "bun:test"; +import { makeTree } from "harness"; import { readFileSync } from "node:fs"; -import { cp, mkdtemp, rm } from "node:fs/promises"; +import { cp, mkdir, mkdtemp, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join, relative } from "node:path"; @@ -13,7 +14,7 @@ const FIXTURE_SOURCE_DIR = fileURLToPath(import.meta.resolve("./fixture")); const TSCONFIG_SOURCE_PATH = join(BUN_REPO_ROOT, "src/cli/init/tsconfig.default.json"); const BUN_TYPES_PACKAGE_JSON_PATH = join(BUN_TYPES_PACKAGE_ROOT, "package.json"); const BUN_VERSION = (process.env.BUN_VERSION ?? Bun.version ?? process.versions.bun).replace(/^.*v/, ""); -const BUN_TYPES_TARBALL_NAME = `types-bun-${BUN_VERSION}.tgz`; +const BUN_TYPES_TARBALL_NAME = `bun-types-${BUN_VERSION}.tgz`; const { config: sourceTsconfig } = ts.readConfigFile(TSCONFIG_SOURCE_PATH, ts.sys.readFile); @@ -26,44 +27,55 @@ const DEFAULT_COMPILER_OPTIONS = ts.parseJsonConfigFileContent( const $ = Shell.cwd(BUN_REPO_ROOT); let TEMP_DIR: string; -let FIXTURE_DIR: string; +let TEMP_FIXTURE_DIR: string; beforeAll(async () => { TEMP_DIR = await mkdtemp(join(tmpdir(), "bun-types-test-")); - FIXTURE_DIR = join(TEMP_DIR, "fixture"); + TEMP_FIXTURE_DIR = join(TEMP_DIR, "fixture"); try { - await $`mkdir -p ${FIXTURE_DIR}`; + await $`mkdir -p ${TEMP_FIXTURE_DIR}`; - await cp(FIXTURE_SOURCE_DIR, FIXTURE_DIR, { recursive: true }); + await cp(FIXTURE_SOURCE_DIR, TEMP_FIXTURE_DIR, { recursive: true }); await $` cd ${BUN_TYPES_PACKAGE_ROOT} bun install - - # temp package.json with @types/bun name and version cp package.json package.json.backup `; const pkg = await Bun.file(BUN_TYPES_PACKAGE_JSON_PATH).json(); - await Bun.write( - BUN_TYPES_PACKAGE_JSON_PATH, - JSON.stringify({ ...pkg, name: "@types/bun", version: BUN_VERSION }, null, 2), - ); + await Bun.write(BUN_TYPES_PACKAGE_JSON_PATH, JSON.stringify({ ...pkg, version: BUN_VERSION }, null, 2)); await $` cd ${BUN_TYPES_PACKAGE_ROOT} bun run build - bun pm pack --destination ${FIXTURE_DIR} + bun pm pack --destination ${TEMP_FIXTURE_DIR} rm CLAUDE.md mv package.json.backup package.json - cd ${FIXTURE_DIR} - bun uninstall @types/bun || true - bun add @types/bun@${BUN_TYPES_TARBALL_NAME} + cd ${TEMP_FIXTURE_DIR} + bun add bun-types@${BUN_TYPES_TARBALL_NAME} rm ${BUN_TYPES_TARBALL_NAME} `; + + const atTypesBunDir = join(TEMP_FIXTURE_DIR, "node_modules", "@types", "bun"); + console.log("Making tree", atTypesBunDir); + + await mkdir(atTypesBunDir, { recursive: true }); + await makeTree(atTypesBunDir, { + "index.d.ts": '/// ', + "package.json": JSON.stringify({ + "private": true, + "name": "@types/bun", + "version": BUN_VERSION, + "projects": ["https://bun.sh"], + "dependencies": { + "bun-types": BUN_VERSION, + }, + }), + }); } catch (e) { if (e instanceof Bun.$.ShellError) { console.log(e.stderr.toString()); @@ -85,7 +97,7 @@ async function diagnose( const tsconfig = config.options ?? {}; const extraFiles = config.files; - const glob = new Bun.Glob("**/*.{ts,tsx}").scan({ + const glob = new Bun.Glob("./*.{ts,tsx}").scan({ cwd: fixtureDir, absolute: true, }); @@ -180,7 +192,7 @@ function checkForEmptyInterfaces(program: ts.Program) { for (const symbol of globalSymbols) { // find only globals - const declarations = symbol.declarations || []; + const declarations = symbol.declarations ?? []; const concernsBun = declarations.some(decl => decl.getSourceFile().fileName.includes("node_modules/@types/bun")); @@ -240,7 +252,7 @@ afterAll(async () => { describe("@types/bun integration test", () => { test("checks without lib.dom.d.ts", async () => { - const { diagnostics, emptyInterfaces } = await diagnose(FIXTURE_DIR); + const { diagnostics, emptyInterfaces } = await diagnose(TEMP_FIXTURE_DIR); expect(emptyInterfaces).toEqual(new Set()); expect(diagnostics).toEqual([]); @@ -263,9 +275,9 @@ describe("@types/bun integration test", () => { `; test("checks without lib.dom.d.ts and test-globals references", async () => { - const { diagnostics, emptyInterfaces } = await diagnose(FIXTURE_DIR, { + const { diagnostics, emptyInterfaces } = await diagnose(TEMP_FIXTURE_DIR, { files: { - "reference-the-globals.ts": `/// `, + "reference-the-globals.ts": `/// `, "my-test.test.ts": code, }, }); @@ -275,17 +287,81 @@ describe("@types/bun integration test", () => { }); test("test-globals FAILS when the test-globals.d.ts is not referenced", async () => { - const { diagnostics, emptyInterfaces } = await diagnose(FIXTURE_DIR, { - files: { "my-test.test.ts": code }, // no reference to bun/test-globals + const { diagnostics, emptyInterfaces } = await diagnose(TEMP_FIXTURE_DIR, { + files: { "my-test.test.ts": code }, // no reference to bun-types/test-globals }); expect(emptyInterfaces).toEqual(new Set()); // should still have no empty interfaces - expect(diagnostics).not.toEqual([]); + expect(diagnostics).toEqual([ + { + "code": 2582, + "line": "my-test.test.ts:2:48", + "message": + "Cannot find name 'test'. Do you need to install type definitions for a test runner? Try \`npm i --save-dev @types/jest\` or \`npm i --save-dev @types/mocha\`.", + }, + { + "code": 2582, + "line": "my-test.test.ts:3:46", + "message": + "Cannot find name 'it'. Do you need to install type definitions for a test runner? Try \`npm i --save-dev @types/jest\` or \`npm i --save-dev @types/mocha\`.", + }, + { + "code": 2582, + "line": "my-test.test.ts:4:52", + "message": + "Cannot find name 'describe'. Do you need to install type definitions for a test runner? Try \`npm i --save-dev @types/jest\` or \`npm i --save-dev @types/mocha\`.", + }, + { + "code": 2304, + "line": "my-test.test.ts:5:50", + "message": "Cannot find name 'expect'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:6:53", + "message": "Cannot find name 'beforeAll'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:7:54", + "message": "Cannot find name 'beforeEach'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:8:53", + "message": "Cannot find name 'afterEach'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:9:52", + "message": "Cannot find name 'afterAll'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:10:61", + "message": "Cannot find name 'setDefaultTimeout'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:11:48", + "message": "Cannot find name 'mock'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:12:49", + "message": "Cannot find name 'spyOn'.", + }, + { + "code": 2304, + "line": "my-test.test.ts:13:44", + "message": "Cannot find name 'jest'.", + }, + ]); }); }); test("checks with no lib at all", async () => { - const { diagnostics, emptyInterfaces } = await diagnose(FIXTURE_DIR, { + const { diagnostics, emptyInterfaces } = await diagnose(TEMP_FIXTURE_DIR, { options: { lib: [], }, @@ -295,8 +371,29 @@ describe("@types/bun integration test", () => { expect(diagnostics).toEqual([]); }); + test("fails with types: [] and no jsx", async () => { + const { diagnostics, emptyInterfaces } = await diagnose(TEMP_FIXTURE_DIR, { + options: { + lib: [], + types: [], + jsx: ts.JsxEmit.None, + }, + }); + + expect(emptyInterfaces).toEqual(new Set()); + expect(diagnostics).toEqual([ + // This is expected because we, of course, can't check that our tsx file is passing + // when tsx is turned off... + { + "code": 17004, + "line": "[slug].tsx:17:10", + "message": "Cannot use JSX unless the '--jsx' flag is provided.", + }, + ]); + }); + test("checks with lib.dom.d.ts", async () => { - const { diagnostics, emptyInterfaces } = await diagnose(FIXTURE_DIR, { + const { diagnostics, emptyInterfaces } = await diagnose(TEMP_FIXTURE_DIR, { options: { lib: ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable"].map(name => `lib.${name.toLowerCase()}.d.ts`), },