This document covers breaking changes introduced across TypeScript 5.0 through 5.9 that are likely to surface during a routine upgrade. These accumulate — upgrading from 5.0 to 6.0 means encountering all of them.
## All enums are now union enums

Every `enum` member now gets its own unique type. Previously only enums with literal initializers were treated as union enums. This can break code that assigns computed/non-literal enum values across enum boundaries.

```ts
enum E { A = Math.random() }
declare let e: E;
declare let n: number;
e = n; // Error: Type 'number' is not assignable to type 'E'
```

Fix: use an explicit cast `e = n as E` or restructure the code.

## `--module` and `--moduleResolution` must agree on Node.js settings

When using `node16` or `nodenext` for either `--module` or `--moduleResolution`, the other must also use a Node.js-related setting. Mismatches like `--module esnext --moduleResolution node16` are now rejected.

Fix: use `--module nodenext` alone, or use `--module esnext --moduleResolution bundler`.

## Consistent export checking for merged symbols in ambient contexts

When two declarations merge in ambient contexts (declaration files, `declare module` blocks), they must now agree on whether they are both exported.

```ts
declare module 'my-lib' {
    export function foo(): void;
    export {};
    namespace foo { // Error: must also be exported
        export function bar(): void;
    }
}
```

Fix: add `export` to both, or remove `export {}` so all declarations are implicitly exported.

## `super` property accesses on instance fields now error

TypeScript now errors when `super.x` refers to a class field (as opposed to a prototype method), since this is undefined at runtime.

```ts
class Base {
    myMethod = () => {};
}
class Derived extends Base {
    call() {
        super.myMethod(); // Error: 'super' cannot access instance fields
    }
}
```

This is always a usercode bug.

## More accurate conditional type constraints

A conditional type like `T extends Foo ? A : B` where `T` is generic no longer eagerly resolves to the false branch when `T`'s constraint doesn't extend `Foo`. It produces a union instead.

```ts
type IsArray<T> = T extends any[] ? true : false;
function foo<U extends object>(x: IsArray<U>) {
    let second: false = x; // Error (previously allowed)
}
```

Fix: don't assume the conditional type resolves to a single branch when the type parameter is generic. This will likely be a complicated fix.

## More aggressive reduction of intersections with type variables and primitives

Intersections of type variables with primitives are now reduced more aggressively. `T & number` becomes `never` if `T`'s constraint is known to be incompatible with `number`.

```ts
function foo<T extends "abc" | "def">(x: T, num: number) {
    let b = intersect(x, num); // Was 'T & number', now is 'never'
}
```

The correct fix is context-dependent.

## Errors when type-only imports conflict with local values (under `isolatedModules`)

A type-only import that has the same name as a local value declaration is now an error:

```ts
import { Something } from "./some/path";
let Something = 123; // Error under isolatedModules
```

Fix: add the `type` modifier to the import, or rename the local variable.

## Enum members cannot be named `Infinity`, `-Infinity`, or `NaN`

```ts
enum E {
    Infinity = 0,  // Error
    NaN = 2,       // Error
}
```

This is always a usercode bug.

## Inferred type predicates from `filter`-like callbacks

TypeScript now infers type predicates for filter-like functions. Code that relied on the wider pre-filter type may break:

```ts
// Previously: nums was (number | null)[]
// Now: nums is number[]
const nums = [1, 2, 3, null].filter(x => x !== null);
nums.push(null); // Error in 5.5+
```

Fix: add an explicit type annotation if the wider type was intended instead.

## Regular expression syntax checking

Invalid patterns, non-existent backreferences, and features unavailable at the configured `target` now produce errors:

```ts
let re = /@typedef \{import\((.+)\)\} \3/u;
//                                     ~
// Error: backreference \3 refers to group that doesn't exist
```

Fix: correct the regex, if the intended meaning was clear, or upgrade `target` to the necessary ES version that supports the needed regex feature. Otherwise, treat as a usercode bug.

## Disallowed nullish and truthy checks

Expressions that are syntactically always truthy or always nullish now error:

```ts
if (/0x[0-9a-f]/) { } // Forgot to call .test(s) ?
if (x => 0) { }       // Probably meant to write <=
value < options.max ?? 100; // Missing parens on right side
```

If the intended meaning was obvious (usually missing parens), fix it, otherwise flag as a usercode bug.

## Respecting file extensions and `package.json` inside `node_modules`

TypeScript now reads `.mjs`/`.cjs` extensions and `package.json` `"type"` fields inside `node_modules` in all module modes, not only under `node16`/`nodenext`. Previously-valid default imports from ESM packages may now error:

```ts
// May now fail if "dep" is ESM and this is a default import in a CJS context
import dep from "dep";
```

Fix: use namespace import `import * as dep from "dep"` or switch module settings.

## Correct `override` checks on computed properties

Computed properties marked `override` now properly check for the existence of a base class member, and `noImplicitOverride` now catches missing `override` on computed properties that shadow base members.

## Checks for never-initialized variables

Variables that are _never_ initialized (not just possibly uninitialized) now error when accessed from nested functions:

```ts
function foo() {
    let result: number;
    function printResult() {
        console.log(result); // Error: used before assigned
    }
}
```

Unless the fix is obvious, treat this as a usercode bug.

## `TypedArray`s are now generic over `ArrayBufferLike`

All `TypedArray` types (e.g. `Uint8Array`) now have a type parameter for the buffer type. This can break code using Node.js `Buffer`:

```
error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array<ArrayBufferLike>'.
```

Fix: update `@types/node` to the latest version.

## Validated JSON imports in `--module nodenext`

JSON file imports now require an import attribute with `type: "json"`:

```diff
- import myConfig from "./myConfig.json";
+ import myConfig from "./myConfig.json" with { type: "json" };
```

Also, named exports from JSON are no longer available; use the `default` import instead.

## Granular checks for branches in return expressions

Each branch of a conditional expression in a `return` statement is now independently checked against the return type:

```ts
function getUrl(s: string): URL {
    return cache.has(s) ? cache.get(s) : s;
    //                                   ~
    // Error: 'string' is not assignable to 'URL'
}
```

Unless the fix is obvious, treat this as a usercode bug.

## Import assertions replaced by import attributes

The `assert` keyword is no longer accepted; use `with`:

```diff
- import data from "./data.json" assert { type: "json" };
+ import data from "./data.json" with { type: "json" };
```


# TypeScript 6.0 Migration

TypeScript 6.0 changes many defaults.
When upgrading to TS 6.0, you will want to explicitly set any config value that would be inferred differently to avoid unwanted changes.

| Setting                        | Old Default           | New Default              | Quick Fix                                                       |
| ------------------------------ | --------------------- | ------------------------ | ---------------------------------------------------------       |
| `strict`                       | `false`               | `true`                   | Add `"strict": false` to restore old behavior                   |
| `types`                        | `["*"]` (all @types)  | `[]` (none)              | Depends                                                         |
| `rootDir`                      | inferred from sources | `.` (tsconfig directory) | Add `"rootDir": "./src"`                                        |
| `module`                       | `commonjs`            | `esnext`                 | Determine correct setting based on project                      |
| `target`                       | `es5`                 | `es2025`                 | Set to ES6 if the project really needs to target ancient stacks |
| `noUncheckedSideEffectImports` | `false`               | `true`                   | Set to `false` to restore old behavior *if needed*              |
| `libReplacement`               | `true`                | `false`                  | Set to `true` to restore old behavior *if needed*               |

During 6.0 migration, you *must* check `rootDir`. If all TS files are under `src`, you *must* set `rootDir` since otherwise TS will output to e.g. `./outDir/src` instead.

## `types` directives

With `types` defaulting to `[]`, you may need to add entries to this array depending on the intended global environment:
 * `Buffer`, modules like `"fs"`, `"path"`, etc: Add `"node"`
 * `describe`, `it`, etc: add the correct test framework (`"jest"`, `"mocha"`, etc)

## `'rootDir' is expected to contain all source files`**

Explicitly add the appropriate `rootDir`
```diff
 {
   "compilerOptions": {
+    "rootDir": "./src"
   }
 }
```

## Error: `Option 'moduleResolution' with value 'node10' is deprecated`** (or `classic`)

For Node.js projects:

```diff
 {
   "compilerOptions": {
-    "module": "commonjs"
+    "module": "nodenext"
   }
 }
```

For web projects using a bundler:

```diff
 {
   "compilerOptions": {
-    "module": "commonjs",
-    "moduleResolution": "node10"
+    "module": "esnext",
+    "moduleResolution": "bundler"
   }
 }
```

## Error: `Option 'baseUrl' is deprecated`**

If you can simply remove `baseUrl`, do. However, if the tsconfig has `paths` entries, run `npx -y @andrewbranch/ts5to6 --fixBaseUrl tsconfig.json` to fix path mappings automatically

## Error: `Option 'outFile' is deprecated`

Abort and tell the user they need to use their own bundler now.

## Error: `Option 'module' with value 'amd' is deprecated`** (or `system`, `umd`):

Abort and tell the user they need to pick a new module format

## `Option 'esModuleInterop' cannot be set to 'false'` (or `allowSyntheticDefaultImports`)

These are always-on in TypeScript 6.0. Remove the explicit `false` setting. You may need to update imports:

```diff
- import * as express from "express";
+ import express from "express";
```

---

## Option 'downlevelIteration' is deprecated`

Only had effect on ES5 emit, which is also deprecated. Remove the setting from the config entirely.

## TS2307 on private package subpaths

**Symptom:** After switching `moduleResolution` to `bundler` (or `node16` / `nodenext`), imports that reach into a package's internals fail:

```
src/utils/zod.ts(3,26): error TS2307: Cannot find module 'zod/v4/core/util.cjs'
```

Under these resolvers TypeScript honors the package's `exports` map, so subpaths that the package does not publish are no longer reachable — even if the file exists on disk.

**Fix:**

Find the public `exports`-mapped path that exposes the same symbol.
Every well-maintained package publishes types through its `exports` map — the private subpath exists only for the package's own internal use. Look in order:

1. Check the package's top-level export (`import { X } from 'pkg'`).
2. Check documented sub-entry-points in the `exports` field of `package.json` (e.g. `zod/v4`, `zod/v4/core`).
3. Run `node -e "console.log(Object.keys(require('pkg/package.json').exports))"` to see all published paths at once.
4. Search the package's type declaration files for the symbol name to locate which public entry re-exports it.

Do **not** fall back to inlining a local copy of the type.
That creates drift and hides the real fix.
If you cannot locate a public re-export after the steps above, open the package's repo and search its `exports` map directly — it is almost always there under a different entry point than the one the code was using.

Do not work around this by disabling `exports` resolution or downgrading `moduleResolution`.
