[Tracking] TypeScript 5.4 → 6.0 migration
- Dominant language
- Python
- Stars
- 74.8k
- Forks
- 18.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 692
Description
## Goal
Migrate Apache Superset's frontend from **TypeScript 5.4.5** (current pin) to **TypeScript 6.0.3**, preserving green CI throughout by landing a chain of small, forward-compatible PRs rather than one big-bang upgrade.
Each PR in the chain:
- **Compiles cleanly under both TS 5.4.5 (current CI) and TS 6.0.3.**
- Is scoped to one package (or one cohesive group), so reviews stay reviewable.
- Contains **no runtime behaviour changes** — only type-level adjustments, targeted `as unknown as ...` casts at library boundaries where our narrower types are known-safe, forward-compat widenings to match library signatures, module declarations for bundler-only assets, and similar.
- Leaves master with strictly fewer TS 6.0 errors than before.
The final PR in the chain pins `typescript@6.0.3` in `superset-frontend/package.json` and `package-lock.json`.
## Why forward-compat
TS 6.0 tightens inference in many places (caught values as `unknown`, stricter JSX attribute checking, stricter `forwardRef` callback signatures, deprecation of `baseUrl`, strict property initialization enforced harder, `Iterator.value` as `T | undefined` until narrowed, etc.). Landing a single large "bump TS + fix 500+ errors" PR would:
- Be unreviewable.
- Touch every package simultaneously, making bisection painful if something regresses.
- Create a long-running branch that constantly conflicts with master.
The forward-compat approach splits the work so each PR lands on green CI, can be merged independently, and contributes a demonstrable reduction in the TS 6.0 error count.
## Rules for all PRs in this chain
- **No `any` types.** Use proper TypeScript (per `CLAUDE.md` / project standards). Where library boundaries force widening, use targeted `as unknown as ` casts at the JSX/call boundary, not `as any`.
- **Prefer signature widening over casting** when the fix lives in code we own (e.g. `FetchRetryOptions` in `connection/types`).
- **Prefer destructuring** over casting when the root cause is a spread overriding an explicit attribute (e.g. Phase B's `AntdEnhanced` fix — `{...rest}` was silently overriding an explicit `component={...}` because `IconType.component` is wider than `BaseIconProps['component']`).
- **Comment any cast** with a short "Forward-compat: TS 6.0 …" note explaining why the cast is safe.
- **Run `pre-commit run` on staged files before pushing.** (Per `CLAUDE.md` — non-negotiable.) The repo's `type-checking-frontend` hook requires `superset-ui-core/lib/` and `superset-core/lib/` to be built first. Run `npx tsc --build` in those packages once per fresh worktree.
- **Stay per-package.** Don't fix errors outside the package scope, even if tempted. A later phase will get them.
## How to reproduce the TS 6.0 baseline in a fresh worktree
```bash
# From superset-frontend/
npm install typescript@6.0.3 --no-save
# Scope to a single package (recommended for per-phase measurement):
NODE_OPTIONS="--max-old-space-size=8192" npx tsc \
--project packages//tsconfig.json \
--noEmit \
--ignoreDeprecations 6.0
```
The `--ignoreDeprecations 6.0` CLI flag silences the `TS5101` `baseUrl` deprecation warning long enough to see the real source errors. (See "Known irreducible conflict" below.)
When done, restore the pinned compiler:
```bash
npm install typescript@5.4.5 --no-save
```
## Known irreducible conflict: `baseUrl` TS5101
- TS 6.0 emits `TS5101: Option 'baseUrl' is deprecated…` against `compilerOptions.baseUrl` in both `superset-frontend/tsconfig.json` (root) and every package tsconfig that overrides it.
- The standard silencer is `"ignoreDeprecations": "6.0"` in `tsconfig.json`. **However, TS 5.4.5 only accepts `"5.0"` and rejects `"6.0"` with `Invalid value for --ignoreDeprecations`.** `"5.0"` does not silence TS 6.0's own deprecations.
- Dropping the per-package `baseUrl` override does not help: TS 6.0 still emits TS5101 at the `extends` line because the option is inherited.
- **Decision:** This is a config-level issue, not a per-package issue. It will be handled in a dedicated "root tsconfig cleanup" PR (see below), likely simultaneously with the final TS 6.0.3 pin. Individual per-package PRs ignore it.
## Phase breakdown
Dependency order (leaf → app). Phases B and C are scoped per-package; later phases may batch small leaves.
### Phase A — measurement/scaffolding (not a PR) ✅
Initial exploration on a throwaway branch. Established the per-phase forward-compat approach, identified the TS5101 conflict above, catalogued the rough shape of errors per package. Superseded by the phase PRs; nothing to merge.
### Phase B — `packages/superset-ui-core` ✅ PR open: #39535
**Status:** In review. 15 files changed, +190/-45 lines. 24+ TS 6.0 errors in scope → 0.
**Fix categories applied (useful reference for later phases):**
- `forwardRef` callback refs: `RefObject` → `ForwardedRef`; add `typeof ref !== 'function'` guard before `ref.current = ...`.
- antd sorter / filter / handler boundary: `as unknown as BaseOptionType | DefaultOptionType` (for sorters), `as unknown as SelectHandler<...>` (for deselect/select), `as unknown as TableProps['...']` (for Table props). Our handlers read only fields that always exist on the narrower type.
- `IconType.component` spread override: destructure the wider-typed `component` out of spread so explicit JSX attribute wins.
- `react-resize-detector` 6.0 callback: accept `width?: number`, default missing to `0`.
- `FetchRetryOptions`: widen `retryDelay` / `retryOn` to accept `Error | null` / `Response | null` (matches `fetch-retry`).
- `catch (caught)` renaming + narrow via `Parameters[0]` for TS 6.0's `unknown` default.
- `Map` iterator `.value`: guard `!== undefined` before `Map#delete`.
- `InteractiveTableUtils.columnRef = null` for strict property init.
- `declare module '*.css'` in `types/assets.d.ts`.
Branch: `ts6-migration/phase-b-superset-ui-core`
### Phase C — `packages/superset-core` ✅ PR open: #39537
**Status:** In review. 2 files changed, +13/-2 lines. 9 TS 6.0 source errors (7× TS2882, 2× TS2564) → 0.
**Fixes:**
- `src/theme/Theme.tsx`: `theme!: SupersetTheme` and `private antdConfig!: AntdThemeConfig` (definite-assignment; both assigned via `setConfig()` in constructor).
- `types/external.d.ts`: `declare module '@fontsource/*'` for CSS side-effect imports in `theme/GlobalStyles.tsx`.
Branch: `ts6-migration/phase-c-superset-core`
### Phase E — small leaf packages (can run in parallel with B/C review) 📋
**Scope:** `packages/generator-superset` + `packages/superset-ui-switchboard`. Both have **no Superset-internal dependencies**, so Phase E can branch off bare master without any cherry-picking or stacking.
**Measured TS 6.0 errors:**
- `generator-superset`: 0 source errors. Likely becomes part of the config cleanup PR (just covered by the root-level type-check passing).
- `superset-ui-switchboard`: 3 × TS2564 (strict property init):
- `src/switchboard.ts:91` — `port`
- `src/switchboard.ts:100` — `debugMode`
- `src/switchboard.ts:102` — `isInitialised`
Fix pattern: add `!` definite-assignment assertions (same as Phase C `Theme.tsx`), assuming each field is assigned before first use. Read the constructor and `start()` method to confirm.
**Expected PR size:** ~1 file, ~3-line change. Title: `chore(superset-ui-switchboard): forward-compat fixes for TypeScript 6.0`.
**Branching:** `ts6-migration/phase-e-small-leaves` off `origin/master`.
### Phase D — `packages/superset-ui-chart-controls` 📋 (blocked on B)
**Must wait for Phase B (#39535) to merge.** Chart-controls depends on `@superset-ui/core` (peer dep) and `@apache-superset/core` (direct dep). Until B lands, ui-core's composite `lib/` can't build cleanly under TS 6.0, which produces 55× spurious TS6305 errors plus cascading `any` types in chart-controls. Once B lands, the measurement clears up.
**Current (pre-B-merge) TS 6.0 measurement, for reference only:**
- 22 × TS7006 (implicit `any` — mostly parameter types on callbacks that were previously inferred through ui-core's types)
- 19 × TS2322 (type mismatches — likely similar boundary issues to Phase B)
- 1 × TS2538 (`shared-controls/customControls.tsx:168` — `Type 'unknown' cannot be used as an index type`)
Many of these TS7006/TS2322 errors likely disappear once B's fixes narrow ui-core's exported types properly. **Re-measure after B merges before fixing.**
**Approach:**
1. Wait for #39535 (B) to merge.
2. Pull master. Rebase phase-b-ui-core lib build cache (`npx tsc --build packages/superset-ui-core`).
3. Branch `ts6-migration/phase-d-superset-ui-chart-controls` off updated master.
4. Install TS 6.0.3, re-measure. Expect the number of real errors to drop significantly.
5. Fix remaining errors using the Phase B fix-category playbook above.
6. Downgrade to TS 5.4.5, `pre-commit run`, commit, push, PR.
### Phase F — `superset-frontend/src/` (main app) 📋 (blocked on B, C, D)
**The big one.** Consumes everything above. Save for last.
**Approach:** same as Phase D but scoped to the main app. Re-measure after D lands — many errors in the app are cascaded from package types that D will fix. Expect the largest raw file count; plan to split this into sub-phases (e.g. F1: `src/explore`, F2: `src/dashboard`, F3: `src/SqlLab`, F4: everything else) if the diff gets unreviewable.
### Config cleanup PR — root tsconfig + final pin 📋 (last)
After all package phases land:
1. Migrate the root `superset-frontend/tsconfig.json` off `baseUrl` (paths can resolve relative to the tsconfig itself in modern TS), OR add `"ignoreDeprecations": "6.0"` if staying on `baseUrl` a bit longer. The migration-off-baseUrl path is preferred; `ignoreDeprecations` just kicks the can to TS 7.0.
2. Pin `typescript@6.0.3` in `superset-frontend/package.json` and regenerate `package-lock.json`.
3. Run the full monorepo `npm run type` on TS 6.0.3 and confirm zero errors.
4. Document the upgrade in `UPDATING.md`.
## Session-restart prompt template (for future Claude sessions)
If this work resumes in a new session, the agent can pick up any remaining phase with:
> I'm continuing the TypeScript 5.4 → 6.0 migration tracked in [this issue link]. The goal is forward-compatible fixes that compile under both TS 5.4.5 (current CI pin) and TS 6.0.3, scoped per-package. Please read the tracking issue for full context, rules, and the fix-category playbook from Phase B. Then pick up **Phase ** — [package name]. Follow the approach under that phase in the issue. Do not touch files outside that package. When done, run `pre-commit run`, open a PR titled `chore(): forward-compat fixes for TypeScript 6.0`, and update this issue's checklist.
## PR checklist
- [x] Phase B — `packages/superset-ui-core` — #39535
- [x] Phase C — `packages/superset-core` — #39537
- [ ] Phase E — `packages/superset-ui-switchboard` (+ `generator-superset` if any errors appear)
- [ ] Phase D — `packages/superset-ui-chart-controls`
- [ ] Phase F — `superset-frontend/src/` (may be split F1/F2/…)
- [ ] Config cleanup — root tsconfig `baseUrl` migration + `typescript@6.0.3` pin + `UPDATING.md` entry
## Worktree layout (current sessions)
- `ts6-migration/phase-b-superset-ui-core` — Phase B PR branch
- `ts6-migration/phase-c-superset-core` — Phase C PR branch
Contributor guide
Research direction
For the remaining small leaf task, read packages/superset-ui-switchboard/src/switchboard.ts, especially the constructor and start() method, then measure the package with TypeScript 6.0.3 using its tsconfig. Confirm the three reported fields are initialized before use, verify the package under both TypeScript 5.4.5 and 6.0.3, and run pre-commit on staged files.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- frontend
- Issue type
- Refactor
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100