Skip to content

[Tracking] TypeScript 5.4 → 6.0 migration #39539

@rusackas

Description

@rusackas

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 <specific type> 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

# 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/<pkg>/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:

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<T>ForwardedRef<T>; 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<object>['...'] (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<typeof getClientErrorObject>[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:91port
    • src/switchboard.ts:100debugMode
    • src/switchboard.ts:102isInitialised

    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:168Type '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 chore(superset-ui-core): forward-compat fixes for TypeScript 6.0 (Phase B) #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(<package>): forward-compat fixes for TypeScript 6.0, and update this issue's checklist.

PR checklist

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    change:frontendRequires changing the frontend

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions