Skip to content

Add node and vitest/globals to tsconfig types - #2276

Open
arnaud-dezandee wants to merge 2 commits into
freelensapp:mainfrom
arnaud-dezandee:tsconfig-add-vitest-globals-types
Open

Add node and vitest/globals to tsconfig types#2276
arnaud-dezandee wants to merge 2 commits into
freelensapp:mainfrom
arnaud-dezandee:tsconfig-add-vitest-globals-types

Conversation

@arnaud-dezandee

Copy link
Copy Markdown
Contributor

Description of changes:

  • Add an explicit compilerOptions.types array (["node", "vitest/globals"]) to the root tsconfig.json and the shared packages/infrastructure/typescript/config/base.json.
  • Makes Vitest globals (describe, it, expect, etc.) and Node types available to the type checker without a per-file import, so test files can rely on ambient globals.

@dex4er dex4er added this to the v2.0.0 milestone Jul 22, 2026
@dex4er dex4er mentioned this pull request Jul 22, 2026
@dex4er

dex4er commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

@arnaud-dezandee Good eye, thanks! However, currently our globals: true settings is temporary as the settings will change after transition to React 18 or 19.

I'm putting it on the list for post-transition tasks.

@dex4er

dex4er commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

@arnaud-dezandee Actually you can give me a hint, if we should disable any globals, then use explicit imports from vitest or use it as a global. However, I would like to avoid adding anything other than nodejs globals to the default settings.

Even right now we have several conflicts between nodejs vs WHATWG types.

Anyway, I would like to postpone the discussion and decision after the main transition.

@dex4er

dex4er commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

@claude should we add vitest globals to the separate tsconfig or use explicit imports? We want:

  1. Not break type checks for tests and normal code
  2. Editor should not show us false errors
  3. Globals should not be too wide, as ie. vitest globals should not be applied to the normal code

@claude

claude Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dex4er's task in 7m 32s —— View job


Recommendation

Merge this PR as-is now, enforce requirement 3 with a lint rule rather than with the type system, and switch to explicit imports (globals: false) after the React transition. A separate tsconfig for tests is the one option I would not pick — it is the most plumbing for the least benefit, and it fails silently.

The key constraint: TypeScript's global scope is per-program, not per-file. Any tsconfig whose include covers both src/foo.ts and src/foo.test.ts gives vitest globals to both. So requirement 3 has exactly two possible mechanisms — either don't use globals at all, or split tests into a second program. There is no types-array arrangement that satisfies it.


What the current state actually is

tsconfig.typecheck.json:53 on main already carries "types": ["node", "vitest/globals"], and pnpm type:check is the only thing CI type-checks with. So CI has already been granting vitest globals to production code — this PR does not widen anything there, it only makes editors agree with CI.

What is broken today is the editor, and badly. tsserver picks the nearest tsconfig.json, which is the per-package one (extends: @freelensapp/typescript/config/base.json, include: ["**/*.ts", "**/*.tsx"] — tests included), never tsconfig.typecheck.json. Measured on this checkout:

target main behaviour (--types node) this PR
packages/routing TS2593/TS2304 on every describe/it/expect/vi 0 errors
packages/core 7324 errors, 7248 of them missing test globals 6 errors, all pre-existing TS2883 declaration-emit ones unrelated to types

Also verified: vitest/globals resolves fine from the 9 packages that extend base.json without a vitest devDependency (packages/metrics etc. type-check clean) — vitest is a root devDependency, so TS's upward type-reference resolution finds node_modules/vitest/globals.d.ts.

Options against your three requirements

1. type checks pass 2. no editor false errors 3. globals not applied to normal code
This PR (types: ["node", "vitest/globals"]) yes yes no — but no worse than main, which already leaks in CI
Separate tsconfig.test.json per package yes, with a lot of plumbing fragile yes
globals: false + explicit imports + types: ["node"] yes yes yes

Why not the separate tsconfig. It requires, for all 38 packages plus root: excluding **/*.test.{ts,tsx} (and __tests__/**, src/vitest*.setup.*, @freelensapp/test-utils, which legitimately use vitest APIs outside *.test.*) from the main config, adding a sibling test config, and wiring solution-style references with composite: true so tsserver even loads it — tsserver only auto-discovers tsconfig.json, not tsconfig.test.json. tsconfig.typecheck.json would have to split into two tsc invocations too. The failure mode is the bad part: a test file matching neither project silently falls into an inferred project with default options (no strict, no paths), so it looks clean in the editor while nothing is really checked. For the same reason, a /// <reference types="vitest/globals" /> in a .d.ts is not an alternative — a triple-slash reference in any included file is program-wide, exactly as wide as types.

Why explicit imports is the right end state. It is the only option that satisfies all three at the type level, and it is mechanical: 330 test files, 71 already import from "vitest", so ~259 to convert. @vitest/eslint-plugin (checked v1.6.24) ships prefer-importing-vitest-globals with meta.fixable: "code" — a one-shot npx eslint --fix with a throwaway flat config, then pnpm biome check --write to sort the imports, does the whole conversion. No need to adopt ESLint permanently. Then globals: false in vitest.config.ts (both the root and the per-project copies — the comment at vitest.config.ts:77 is right that it does not inherit) and types drops to ["node"], which is the "nodejs globals only" default you want.

Concrete plan

Now (this PR): merge it. It costs nothing conceptually — it copies the types line CI already uses into the two configs editors actually read.

Now, optionally (cheap, gets you requirement 3 where it matters): enforce it in Biome instead of in TypeScript. style/noRestrictedGlobals (verified against biome 2.5.4) plus an override:

"linter": { "rules": { "style": { "noRestrictedGlobals": { "level": "error", "options": { "deniedGlobals": {
  "describe": "vitest global: only allowed in test files",
  "it": "vitest global: only allowed in test files",
  "test": "vitest global: only allowed in test files",
  "expect": "vitest global: only allowed in test files",
  "vi": "vitest global: only allowed in test files",
  "beforeEach": "vitest global: only allowed in test files",
  "afterEach": "vitest global: only allowed in test files",
  "beforeAll": "vitest global: only allowed in test files",
  "afterAll": "vitest global: only allowed in test files"
} } } } },
"overrides": [
  { "includes": ["**/*.test.ts", "**/*.test.tsx", "**/__tests__/**", "**/vitest*.setup.*",
                 "packages/utility-features/test-utils/**"],
    "linter": { "rules": { "style": { "noRestrictedGlobals": "off" } } } }
]

I ran this over utilities, routing, logger, kube-object: 702 diagnostics across 23 files, all of them test files, zero false positives in production code. Two caveats before enabling repo-wide — the rule unconditionally denies the error and event globals as well (worth a dry run over packages/core, which is DOM-heavy), and it has no autofix.

After the React transition: codemod to explicit imports, globals: false, types: ["node"]. At that point drop the Biome rule again — it becomes redundant.

One note on the PR itself

Setting types also switches off automatic inclusion of every other @types/* package. In this repo that is safe (root node_modules/@types contains only node, and the nested ones under packages/corereact, ws, fs-extra, … — are module-typed, not ambient; verified core, routing and metrics produce no new errors). It is worth a short comment in base.json saying so, because the next person to add an ambient-globals @types package will get a confusing "why is this type missing" and needs to know to add it to the array.

Add the Biome rule →


  • Read the PR diff and the two changed tsconfig files
  • Survey vitest config (globals: true, root + per-project) and the tsconfig extends graph
  • Reproduce the editor false errors on main and verify the PR fixes them
  • Check vitest/globals resolution in packages without a vitest devDependency
  • Confirm packages/core is unaffected (only pre-existing TS2883 remain)
  • Verify the Biome and @vitest/eslint-plugin tooling claims empirically
  • Write up recommendation

No files were changed — this was a question, not an implementation request.
| Branch: tsconfig-add-vitest-globals-types | Model: claude-opus-5

@dex4er

dex4er commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

@claude Rerun the recommendation after React transition is made and now we use React v19

@claude

claude Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Claude finished @dex4er's task in 8m 21s —— View job


Recommendation, re-run against React 19

The blocker you named is cleared: catalog now pins react/react-dom 19.2.8 and @types/react 19.2.17, and the jest -> vitest codemod that globals: true was introduced for (vitest.config.ts:56) is long done. So the answer changes from "postpone" to:

Merge this PR as-is now, then do the explicit-imports switch as a follow-up PR. Drop the Biome-stopgap idea from my previous comment — it was only worth the trouble while the migration was blocked, and it no longer is. A separate test-only tsconfig is still the option I would not pick.

I ran the migration for real on four packages to make sure this is a recommendation and not a hope. It works, and it has two sharp edges you need to know about before starting.


1. The PR itself: still correct, and now safer than it was under React 18

Re-measured on today's tree (origin/main = fffb7a5f), per-package tsconfig.json as tsserver actually loads it:

main this PR
packages/routing 153 errors (TS2593/TS2304 on describe/it/expect) 0
packages/core 7342 errors (3855 TS2593 + 3413 TS2304 + 74 cascading implicit-any) 6
packages/list-layout, metrics, react-application clean clean

CI is unaffected either way — type:check is tsc -p tsconfig.typecheck.json, and tsconfig.typecheck.json:53 has carried "types": ["node", "vitest/globals"] all along. This PR only makes editors agree with CI.

New under React 19: @types/react@19.2.17 has **no declare global block at all— theJSXnamespace now lives insidedeclare namespace React (index.d.ts:4132). Under React 17/18 types it was global, so pinning typesgenuinely risked losing the globalJSXnamespace. That risk is gone. Verified acrosspackages/core/node_modules/@types(16 packages): onlynodeis a script/ambient file;react, react-dom, ws, fs-extra, react-tableand the rest are module-scoped and arrive via imports. The 10 files still writing bareJSX.Element` type-check fine.

So the types restriction costs nothing today. It is still worth a one-line comment in base.json saying that setting types switches off automatic @types/* inclusion, so whoever next adds an ambient-globals @types package knows to add it to the array.

2. One thing to expect after merging: 6 errors appear in packages/core

packages/core does not go to zero. The 6 remaining are TS2883 declaration-portability errors (es-toolkit's DebouncedFuncLeading, @freelensapp/random's GetRandomId) from composite: true in packages/core/tsconfig.json.

They are pre-existing, not caused by this PR — I compiled two of the offending files standalone with --types node and no test file anywhere in the program, and they still fire. They were simply masked: TypeScript skips declaration diagnostics while semantic errors are present, so main's 7342 global errors were hiding them. Merging goes 7342 -> 6, and the 6 are fixed by adding explicit type annotations at the four get-random-*.injectable.ts sites and the two broadcaster* ones. Not a blocker, just don't be surprised.

3. The follow-up migration: proven, with two gotchas

I ran the real thing on routing, logger, utilities, kube-object using @vitest/eslint-plugin@1.6.24's prefer-importing-vitest-globals (meta.fixable: "code"), then pnpm biome check --write, then the test suite. Result: 26 files rewritten, imports sorted and re-quoted correctly by Biome, and 450/450 tests pass. Scale: 332 test files, 71 already importing from "vitest", so ~261 to convert. Working tree restored to clean afterwards.

Two things the rule gets wrong, both found by running it:

(a) It does not import vi. The fixer emits import { describe, it, expect } from "vitest" and leaves vi dangling — TS2304: Cannot find name 'vi' in routing and logger after the pass. 131 test files use vi.* and need a second pass appending vi to the vitest import.

(b) It treats assert as a vitest global and collides with node:assert. In packages/kube-object/src/specifics/pod.test.ts it produced:

import assert from "node:assert";
import { assert, describe, expect, it } from "vitest";

That is TS2300: Duplicate identifier 'assert' and it is not merely a type error — vitest failed to transform the file (Plugin: vite:oxc), 1 test file failed outright. After hand-removing the injected assert, all 9 files and 450 tests passed.

Worse, the rule fired on a production file: it added import { assert } from "vitest" to packages/utility-features/utilities/src/convertMemory.ts, i.e. a vitest import in shipped code. 128 non-test files import node:assert. So the codemod must be scoped to test files only, and assert excluded or post-filtered.

(c) globals: false must also cover 16 non-test files that legitimately use vitest APIs, so they need explicit imports too and must be in the codemod's glob:

vitest.electron-stub.ts
packages/core/__mocks__/{electron,monaco-editor}.ts
packages/core/src/{vitest.setup.tsx,vitest-after-env.setup.ts}
packages/core/src/main/getDiForUnitTesting.ts
packages/core/src/test-utils/{use-fake-time,mock-responses}.ts
packages/core/src/features/cluster/execute/common/testing.ts
packages/core/src/renderer/components/test-utils/get-application-builder.tsx
packages/core/src/renderer/components/dock/logs/__test__/test-utils.ts
packages/utility-features/kube-api/src/mock-responses.ts
freelens/integration/__tests__/*.tests.ts   (4 files, note .tests.ts not .test.ts)

Concrete follow-up plan

  1. Codemod test files + the 16 above with prefer-importing-vitest-globals --fix (throwaway flat config; no need to adopt ESLint).
  2. Second pass to add vi to the vitest import in the 131 files that use it.
  3. Strip the bogus import { assert } from "vitest" wherever node:assert is already imported; confirm zero non-test files were touched.
  4. pnpm biome check --write.
  5. globals: false in vitest.config.ts both at root (:58) and per-project (:78 — the comment there is right that it does not inherit), plus freelens/vitest.integration.config.ts:11.
  6. types -> ["node"] in tsconfig.json, base.json, tsconfig.typecheck.json.

That lands all three of your requirements at the type level, with nodejs globals only in the default settings — no lint rule, no second tsconfig.

Run the explicit-imports migration →

Unrelated finding, worth its own issue

Four package tsconfigs have no include/files of their own:

packages/utility-features/{test-utils,run-many,utilities}/tsconfig.json
packages/kubectl-versions/tsconfig.json

They inherit base.json's "include": ["./styles.d.ts"], which resolves relative to base.json — so their programs contain zero of their own sources. tsc -p in packages/utility-features/utilities exits 0 while checking nothing (that is why the injected import { assert } from "vitest" in convertMemory.ts did not error there; compiling the file directly gives TS2300). Editors fall back to inferred projects for those files, so they get neither strict nor the benefit of this PR. Independent of this change, but the fix is one "include" line each.


  • Confirm the React 19 transition state (react 19.2.8, @types/react 19.2.17)
  • Re-check vitest config, tsconfig extends graph, tsconfig.typecheck.json
  • Re-measure editor errors with/without the PR on the current tree
  • Investigate the 6 TS2883 errors the PR surfaces in packages/core (pre-existing, previously masked)
  • Check the React-19-specific @types/react ambient-globals question
  • Prove the explicit-imports migration end-to-end on 4 real packages (450/450 tests pass)
  • Write updated recommendation

No files were changed — this was a question. All experiments were reverted; working tree is clean.
| Branch: tsconfig-add-vitest-globals-types | Model: claude-opus-5

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants