From a3dda8af420ce76ea46ecaa61a3f2bb547005e36 Mon Sep 17 00:00:00 2001 From: OrcaWin Date: Thu, 15 Oct 2026 20:52:31 -0700 Subject: [PATCH] perf(typecheck): check shared tests only in the Node project (#29381) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> --- .github/CONTRIBUTING.md | 6 +++ .../typecheck-project-coverage.test.mjs | 54 +++++++++++++++++++ config/tsconfig.tc.cli.json | 7 +++ config/tsconfig.tc.web.json | 7 +++ 4 files changed, 74 insertions(+) create mode 100644 config/scripts/typecheck-project-coverage.test.mjs diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index cf1493d14f7..4050eadb0a3 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -60,6 +60,12 @@ pnpm test pnpm build ``` +For repeated local typechecks, use `pnpm tc` (or `pnpm tc:node`, `pnpm tc:web`, +and `pnpm tc:cli`). These keep TypeScript's incremental cache. CI uses +`pnpm typecheck:ci` with Bun for full checks. Shared tests are checked by the +Node project; shared production code is checked in all three environments. +Run `pnpm tc:node` after editing shared tests, or `pnpm tc` to check everything. + Add high-quality tests for behavior changes and bug fixes. Prefer tests that would actually catch a regression, not shallow coverage that only exercises the happy path. If your change affects UI or interaction behavior, verify it on the platforms it could impact. diff --git a/config/scripts/typecheck-project-coverage.test.mjs b/config/scripts/typecheck-project-coverage.test.mjs new file mode 100644 index 00000000000..1d4b7f93c97 --- /dev/null +++ b/config/scripts/typecheck-project-coverage.test.mjs @@ -0,0 +1,54 @@ +import { globSync } from 'node:fs' +import { resolve } from 'node:path' +import { fileURLToPath } from 'node:url' +import ts from 'typescript-api' +import { describe, expect, it } from 'vitest' +import { TYPECHECK_PROJECTS } from './run-typecheck-projects-in-parallel.mjs' + +const root = fileURLToPath(new URL('../..', import.meta.url)) +const isTest = (file) => /\.(?:test|spec)\.(?:[cm]?ts|tsx)$/.test(file) +const projects = TYPECHECK_PROJECTS.filter((project) => project.config.startsWith('config/')).map( + ({ config }) => { + const parsed = ts.getParsedCommandLineOfConfigFile(resolve(root, config), undefined, { + ...ts.sys, + onUnRecoverableConfigFileDiagnostic(diagnostic) { + throw new Error(ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')) + } + }) + if (!parsed || parsed.errors.length) { + throw new Error(`Cannot parse ${config}`) + } + return { config, files: new Set(parsed.fileNames.map((file) => resolve(file))) } + } +) + +function typedFiles(directory) { + return globSync(`${directory}/**/*`, { cwd: root }).filter((file) => + /\.(?:[cm]?ts|tsx)$/.test(file) + ) +} + +describe('typecheck project coverage', () => { + it('checks every shared test exactly once, in the scheduled Node project', () => { + const tests = typedFiles('src/shared').filter(isTest) + expect(tests.length).toBeGreaterThan(0) + const violations = tests.flatMap((file) => { + const owners = projects.filter((project) => project.files.has(resolve(root, file))) + return owners.length === 1 && owners[0].config === 'config/tsconfig.node.json' + ? [] + : [{ file, owners: owners.map((owner) => owner.config) }] + }) + expect(violations).toEqual([]) + }) + + it('keeps all shared production files in every root typecheck project', () => { + const files = typedFiles('src/shared').filter((file) => !isTest(file)) + expect(files.length).toBeGreaterThan(0) + const missing = projects.flatMap((project) => + files + .filter((file) => !project.files.has(resolve(root, file))) + .map((file) => ({ config: project.config, file })) + ) + expect(missing).toEqual([]) + }) +}) diff --git a/config/tsconfig.tc.cli.json b/config/tsconfig.tc.cli.json index c613dd688d9..b587fa5f3e9 100644 --- a/config/tsconfig.tc.cli.json +++ b/config/tsconfig.tc.cli.json @@ -1,5 +1,12 @@ { "extends": "./tsconfig.cli.json", + // Shared tests are checked by tsconfig.node.json; shared production code stays in every project. + "exclude": [ + "../src/shared/**/*.test.ts", + "../src/shared/**/*.test.tsx", + "../src/shared/**/*.spec.ts", + "../src/shared/**/*.spec.tsx" + ], "compilerOptions": { "module": "Node20", "moduleResolution": "node16" diff --git a/config/tsconfig.tc.web.json b/config/tsconfig.tc.web.json index a79e96077b6..7bd6a6a5009 100644 --- a/config/tsconfig.tc.web.json +++ b/config/tsconfig.tc.web.json @@ -1,5 +1,12 @@ { "extends": "./tsconfig.web.json", + // Shared tests are checked by tsconfig.node.json; shared production code stays in every project. + "exclude": [ + "../src/shared/**/*.test.ts", + "../src/shared/**/*.test.tsx", + "../src/shared/**/*.spec.ts", + "../src/shared/**/*.spec.tsx" + ], "include": [ "../src/renderer/src/env.d.ts", "../src/renderer/src/**/*",