From 97f37d2366c7ef5d3d00246372060f8a32bc8c41 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Sun, 26 Jan 2025 19:21:13 -0800 Subject: [PATCH 1/3] 5.6 notes --- .../copy/en/release-notes/TypeScript 5.6.md | 649 ++++++++++++++++++ 1 file changed, 649 insertions(+) create mode 100644 packages/documentation/copy/en/release-notes/TypeScript 5.6.md diff --git a/packages/documentation/copy/en/release-notes/TypeScript 5.6.md b/packages/documentation/copy/en/release-notes/TypeScript 5.6.md new file mode 100644 index 000000000000..6e0a68ef6e7e --- /dev/null +++ b/packages/documentation/copy/en/release-notes/TypeScript 5.6.md @@ -0,0 +1,649 @@ +--- +title: TypeScript 5.6 +layout: docs +permalink: /docs/handbook/release-notes/typescript-5-6.html +oneline: TypeScript 5.6 Release Notes +--- + +## Disallowed Nullish and Truthy Checks + +Maybe you've written a regex and forgotten to call `.test(...)` on it: + +```ts +if (/0x[0-9a-f]/) { + // Oops! This block always runs. + // ... +} +``` + +or maybe you've accidentally written `=>` (which creates an arrow function) instead of `>=` (the greater-than-or-equal-to operator): + +```ts +if (x => 0) { + // Oops! This block always runs. + // ... +} +``` + +or maybe you've tried to use a default value with `??`, but mixed up the precedence of `??` and a comparison operator like `<`: + +```ts +function isValid(value: string | number, options: any, strictness: "strict" | "loose") { + if (strictness === "loose") { + value = +value + } + return value < options.max ?? 100; + // Oops! This is parsed as (value < options.max) ?? 100 +} + +``` + +or maybe you've misplaced a parenthesis in a complex expression: + +```ts +if ( + isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") || + isValid(primaryValue, "loose" || isValid(secondaryValue, "loose")) +) { + // ^^^^ 👀 Did we forget a closing ')'? +} +``` + +None of these examples do what the author intended, but they're all valid JavaScript code. +Previously TypeScript also quietly accepted these examples. + +But with a little bit of experimentation, we found that many *many* bugs could be caught from flagging down suspicious examples like above. +In TypeScript 5.6, the compiler now errors when it can syntactically determine a truthy or nullish check will always evaluate in a specific way. +So in the above examples, you'll start to see errors: + +```ts +if (/0x[0-9a-f]/) { +// ~~~~~~~~~~~~ +// error: This kind of expression is always truthy. +} + +if (x => 0) { +// ~~~~~~ +// error: This kind of expression is always truthy. +} + +function isValid(value: string | number, options: any, strictness: "strict" | "loose") { + if (strictness === "loose") { + value = +value + } + return value < options.max ?? 100; + // ~~~~~~~~~~~~~~~~~~~ + // error: Right operand of ?? is unreachable because the left operand is never nullish. +} + +if ( + isValid(primaryValue, "strict") || isValid(secondaryValue, "strict") || + isValid(primaryValue, "loose" || isValid(secondaryValue, "loose")) +) { + // ~~~~~~~ + // error: This kind of expression is always truthy. +} +``` + +Similar results can be achieved by enabling the ESLint `no-constant-binary-expression` rule, and you can [see some of the results they achieved in their blog post](https://eslint.org/blog/2022/07/interesting-bugs-caught-by-no-constant-binary-expression/); +but the new checks TypeScript performs does not have perfect overlap with the ESLint rule, and we also believe there is a lot of value in having these checks built into TypeScript itself. + +Note that certain expressions are still allowed, even if they are always truthy or nullish. +Specifically, `true`, `false`, `0`, and `1` are all still allowed despite always being truthy or falsy, since code like the following: + +```ts +while (true) { + doStuff(); + + if (something()) { + break; + } + + doOtherStuff(); +} +``` + +is still idiomatic and useful, and code like the following: + +```ts +if (true || inDebuggingOrDevelopmentEnvironment()) { + // ... +} +``` + +is useful while iterating/debugging code. + +If you're curious about the implementation or the sorts of bugs it catches, take a look at [the pull request that implemented this feature](https://github.com/microsoft/TypeScript/pull/59217). + +## Iterator Helper Methods + +JavaScript has a notion of *iterables* (things which we can iterate over by calling a `[Symbol.iterator]()` and getting an iterator) and *iterators* (things which have a `next()` method which we can call to try to get the next value as we iterate). +By and large, you don't typically have to think about these things when you toss them into a `for`/`of` loop, or `[...spread]` them into a new array. +But TypeScript does model these with the types `Iterable` and `Iterator` (and even `IterableIterator` which acts as both!), and these types describe the minimal set of members you need for constructs like `for`/`of` to work on them. + +`Iterable`s (and `IterableIterator`s) are nice because they can be used in all sorts of places in JavaScript - but a lot of people found themselves missing methods on `Array`s like `map`, `filter`, and for some reason `reduce`. +That's why [a recent proposal was brought forward in ECMAScript](https://github.com/tc39/proposal-iterator-helpers) to add many methods (and more) from `Array` onto most of the `IterableIterator`s that are produced in JavaScript. + +For example, every generator now produces an object that also has a `map` method and a `take` method. + +```ts +function* positiveIntegers() { + let i = 1; + while (true) { + yield i; + i++; + } +} + +const evenNumbers = positiveIntegers().map(x => x * 2); + +// Output: +// 2 +// 4 +// 6 +// 8 +// 10 +for (const value of evenNumbers.take(5)) { + console.log(value); +} +``` + +The same is true for methods like `keys()`, `values()`, and `entries()` on `Map`s and `Set`s. + +```ts +function invertKeysAndValues(map: Map): Map { + return new Map( + map.entries().map(([k, v]) => [v, k]) + ); +} +``` + +You can also extend the new `Iterator` object: + +```ts +/** + * Provides an endless stream of `0`s. + */ +class Zeroes extends Iterator { + next() { + return { value: 0, done: false } as const; + } +} + +const zeroes = new Zeroes(); + +// Transform into an endless stream of `1`s. +const ones = zeroes.map(x => x + 1); +``` + +And you can adapt any existing `Iterable`s or `Iterator`s into this new type with `Iterator.from`: + +```ts +Iterator.from(...).filter(someFunction); +``` + +Now, we have to talk about naming. + +Earlier we mentioned that TypeScript has types for `Iterable` and `Iterator`; +however, like we mentioned, these act sort of like "protocols" to ensure certain operations work. +*That means that not every value that is declared `Iterable` or `Iterator` in TypeScript will have those methods we mentioned above.* + +But there is still a new **runtime value** called `Iterator`. +You can reference `Iterator`, as well as `Iterator.prototype`, as actual values in JavaScript. +This is a bit awkward since TypeScript already defines its own thing called `Iterator` purely for type-checking. +So due to this unfortunate name clash, TypeScript needs to introduce a separate type to describe these native/built-in iterable iterators. + +TypeScript 5.6 introduces a new type called `IteratorObject`. +It is defined as follows: + +```ts +interface IteratorObject extends Iterator { + [Symbol.iterator](): IteratorObject; +} +``` + +Lots of built-in collections and methods produce subtypes of `IteratorObject`s (like `ArrayIterator`, `SetIterator`, `MapIterator`, and more), and both the core JavaScript and DOM types in `lib.d.ts`, along with `@types/node`, have been updated to use this new type. + +Similarly, there is a `AsyncIteratorObject` type for parity. +`AsyncIterator` does not yet exist as a runtime value in JavaScript that brings the same methods for `AsyncIterable`s, [but it is an active proposal](https://github.com/tc39/proposal-async-iterator-helpers) and this new type prepares for it. + +We'd like to thank [Kevin Gibbons](https://github.com/bakkot) who contributed [the changes for these types](https://github.com/microsoft/TypeScript/pull/58222), and who is one of the co-authors of [the proposal](https://github.com/tc39/proposal-iterator-helpers). + +## Strict Builtin Iterator Checks (and `--strictBuiltinIteratorReturn`) + +When you call the `next()` method on an `Iterator`, it returns an object with a `value` and a `done` property. +This is modeled with the type `IteratorResult`. + +```ts +type IteratorResult = IteratorYieldResult | IteratorReturnResult; + +interface IteratorYieldResult { + done?: false; + value: TYield; +} + +interface IteratorReturnResult { + done: true; + value: TReturn; +} +``` + +The naming here is inspired by the way a generator function works. +Generator functions can `yield` values, and then `return` a final value - but the types between the two can be unrelated. + +```ts +function abc123() { + yield "a"; + yield "b"; + yield "c"; + return 123; +} + +const iter = abc123(); + +iter.next(); // { value: "a", done: false } +iter.next(); // { value: "b", done: false } +iter.next(); // { value: "c", done: false } +iter.next(); // { value: 123, done: true } +``` + +With the new `IteratorObject` type, we discovered some difficulties in allowing safe implementations of `IteratorObject`s. +At the same time, there's been a long standing unsafety with `IteratorResult` in cases where `TReturn` was `any` (the default!). +For example, let's say we have an `IteratorResult`. +If we end up reaching for the `value` of this type, we'll end up with `string | any`, which is just `any`. + +```ts +function* uppercase(iter: Iterator) { + while (true) { + const { value, done } = iter.next(); + yield value.toUppercase(); // oops! forgot to check for `done` first and misspelled `toUpperCase` + + if (done) { + return; + } + } +} +``` + +It would be hard to fix this on every `Iterator` today without introducing a lot of breaks, but we can at least fix it with most `IteratorObject`s that get created. + +TypeScript 5.6 introduces a new intrinsic type called `BuiltinIteratorReturn` and a new `--strict`-mode flag called `--strictBuiltinIteratorReturn`. +Whenever `IteratorObject`s are used in places like `lib.d.ts`, they are always written with `BuiltinIteratorReturn` type for `TReturn` (though you'll see the more-specific `MapIterator`, `ArrayIterator`, `SetIterator` more often). + +```ts +interface MapIterator extends IteratorObject { + [Symbol.iterator](): MapIterator; +} + +// ... + +interface Map { + // ... + + /** + * Returns an iterable of key, value pairs for every entry in the map. + */ + entries(): MapIterator<[K, V]>; + + /** + * Returns an iterable of keys in the map + */ + keys(): MapIterator; + + /** + * Returns an iterable of values in the map + */ + values(): MapIterator; +} +``` + +By default, `BuiltinIteratorReturn` is `any`, but when `--strictBuiltinIteratorReturn` is enabled (possibly via `--strict`), it is `undefined`. +Under this new mode, if we use `BuiltinIteratorReturn`, our earlier example now correctly errors: + +```ts +function* uppercase(iter: Iterator) { + while (true) { + const { value, done } = iter.next(); + yield value.toUppercase(); + // ~~~~~ ~~~~~~~~~~~ + // error! ┃ ┃ + // ┃ ┗━ Property 'toUppercase' does not exist on type 'string'. Did you mean 'toUpperCase'? + // ┃ + // ┗━ 'value' is possibly 'undefined'. + + if (done) { + return; + } + } +} +``` + +You'll typically see `BuiltinIteratorReturn` paired up with `IteratorObject` throughout `lib.d.ts`. +In general, we recommend being more explicit around the `TReturn` in your own code when possible. + +For more information, you can [read up on the feature here](https://github.com/microsoft/TypeScript/pull/58243). + +## Support for Arbitrary Module Identifiers + +JavaScript allows modules to export bindings with invalid identifier names as string literals: + +```ts +const banana = "🍌"; + +export { banana as "🍌" }; +``` + +Likewise, it allows modules to grab imports with these arbitrary names and bind them to valid identifiers: + +```ts +import { "🍌" as banana } from "./foo" + +/** + * om nom nom + */ +function eat(food: string) { + console.log("Eating", food); +}; + +eat(banana); +``` + +This seems like a cute party trick (if you're as fun as we are at parties), but it has its uses for interoperability with other languages (typically via JavaScript/WebAssembly boundaries), since other languages may have different rules for what constitutes a valid identifier. +It can also be useful for tools that generate code, like esbuild [with its `inject` feature](https://esbuild.github.io/api/#inject). + +TypeScript 5.6 now allows you to use these arbitrary module identifiers in your code! +We'd like to thank [Evan Wallace](https://github.com/evanw) who [contributed this change to TypeScript](https://github.com/microsoft/TypeScript/pull/58640)! + +## The `--noUncheckedSideEffectImports` Option + +In JavaScript it's possible to `import` a module without actually importing any values from it. + +```ts +import "some-module"; +``` + +These imports are often called *side effect imports* because the only useful behavior they can provide is by executing some side effect (like registering a global variable, or adding a polyfill to a prototype). + +In TypeScript, this syntax has had a pretty strange quirk: if the `import` could be resolved to a valid source file, then TypeScript would load and check the file. +On the other hand, if no source file could be found, TypeScript would silently ignore the `import`! + +This is surprising behavior, but it partially stems from modeling patterns in the JavaScript ecosystem. +For example, this syntax has also been used with special loaders in bundlers to load CSS or other assets. +Your bundler might be configured in such a way where you can include specific `.css` files by writing something like the following: + +```tsx +import "./button-component.css"; + +export function Button() { + // ... +} +``` + +Still, this masks potential typos on side effect imports. +That's why TypeScript 5.6 introduces a new compiler option called `--noUncheckedSideEffectImports`, to catch these cases. +When `--noUncheckedSideEffectImports` is enabled, TypeScript will now error if it can't find a source file for a side effect import. + +```ts +import "oops-this-module-does-not-exist"; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// error: Cannot find module 'oops-this-module-does-not-exist' or its corresponding type declarations. +``` + +When enabling this option, some working code may now receive an error, like in the CSS example above. +To work around this, users who want to just write side effect `import`s for assets might be better served by writing what's called an *ambient module declaration* with a wildcard specifier. +It would go in a global file and look something like the following: + +```ts +// ./src/globals.d.ts + +// Recognize all CSS files as module imports. +declare module "*.css" {} +``` + +In fact, you might already have a file like this in your project! +For example, running something like `vite init` might create a similar `vite-env.d.ts`. + +While this option is currently off by default, we encourage users to give it a try! + +For more information, [check out the implementation here](https://github.com/microsoft/TypeScript/pull/58941). + +## The `--noCheck` Option + +TypeScript 5.6 introduces a new compiler option, `--noCheck`, which allows you to skip type checking for all input files. +This avoids unnecessary type-checking when performing any semantic analysis necessary for emitting output files. + +One scenario for this is to separate JavaScript file generation from type-checking so that the two can be run as separate phases. +For example, you could run `tsc --noCheck` while iterating, and then `tsc --noEmit` for a thorough type check. +You could also run the two tasks in parallel, even in `--watch` mode, though note you'd probably want to specify a separate `--tsBuildInfoFile` path if you're truly running them at the same time. + +`--noCheck` is also useful for emitting declaration files in a similar fashion. +In a project where `--noCheck` is specified on a project that conforms to `--isolatedDeclarations`, TypeScript can quickly generate declaration files without a type-checking pass. +The generated declaration files will rely purely on quick syntactic transformations. + +Note that in cases where `--noCheck` is specified, but a project does *not* use `--isolatedDeclarations`, TypeScript may still perform as much type-checking as necessary to generate `.d.ts` files. +In this sense, `--noCheck` is a bit of a misnomer; however, the process will be lazier than a full type-check, only calculating the types of unannotated declarations. +This should be much faster than a full type-check. + +`noCheck` is also available via the TypeScript API as a standard option. +Internally, `transpileModule` and `transpileDeclaration` already used `noCheck` to speed things up (at least as of TypeScript 5.5). +Now any build tool should be able to leverage the flag, taking a variety of custom strategies to coordinate and speed up builds. + +For more information, see [the work done in TypeScript 5.5 to power up `noCheck` internally](https://github.com/microsoft/TypeScript/pull/58364), along with the relevant work to make it publicly available [on the command line](https://github.com/microsoft/TypeScript/pull/58839) and + +## Allow `--build` with Intermediate Errors + +TypeScript's concept of *project references* allows you to organize your codebase into multiple projects and create dependencies between them. +Running the TypeScript compiler in `--build` mode (or `tsc -b` for short) is the built-in way of actually conducting that build across projects and figuring out which projects and files need to be compiled. + +Previously, using `--build` mode would assume `--noEmitOnError` and immediately stop the build if any errors were encountered. +This meant that "downstream" projects could never be checked and built if any of their "upstream" dependencies had build errors. +In theory, this is a very cromulent approach - if a project has errors, it is not necessarily in a coherent state for its dependencies. + +In reality, this sort of rigidity made things like upgrades a pain. +For example, if `projectB` depends on `projectA`, then people more familiar with `projectB` can't proactively upgrade their code until their dependencies are upgraded. +They are blocked by work on upgrading `projectA` first. + +As of TypeScript 5.6, `--build` mode will continue to build projects even if there are intermediate errors in dependencies. +In the face of intermediate errors, they will be reported consistently and output files will be generated on a best-effort basis; +however, the build will continue to completion on the specified project. + +If you want to stop the build on the first project with errors, you can use a new flag called `--stopOnBuildErrors`. +This can be useful when running in a CI environment, or when iterating on a project that's heavily depended upon by other projects. + +Note that to accomplish this, TypeScript now always emits a `.tsbuildinfo` file for any project in a `--build` invocation (even if `--incremental`/`--composite` is not specified). +This is to keep track of the state of how `--build` was invoked and what work needs to be performed in the future. + +You can [read more about this change here on the implementation](https://github.com/microsoft/TypeScript/pull/58838). + +## Region-Prioritized Diagnostics in Editors + +When TypeScript's language service is asked for the *diagnostics* for a file (things like errors, suggestions, and deprecations), it would typically require checking the *entire file*. +Most of the time this is fine, but in extremely large files it can incur a delay. +That can be frustrating because fixing a typo should feel like a quick operation, but can take *seconds* in a big-enough file. + +To address this, TypeScript 5.6 introduces a new feature called *region-prioritized diagnostics* or *region-prioritized checking*. +Instead of just requesting diagnostics for a set of files, editors can now also provide a relevant region of a given file - and the intent is that this will typically be the region of the file that is currently visible to a user. +The TypeScript language server can then choose to provide two sets of diagnostics: one for the region, and one for the file in its entirety. +This allows editing to feel *way* more responsive in large files so you're not waiting as long for thoes red squiggles to disappear. + +For some specific numbers, in our testing [on TypeScript's own `checker.ts`](https://github.com/microsoft/TypeScript/blob/7319968e90600102892a79142fb804bcbe384160/src/compiler/checker.ts), a full semantic diagnostics response took 3330ms. +In contrast, the response for the first region-based diagnostics response took 143ms! +While the remaining whole-file response took about 3200ms, this can make a huge difference for quick edits. + +This feature also includes quite a bit of work to also make diagnostics report more consistently throughout your experience. +Due the way our type-checker leverages caching to avoid work, subsequent checks between the same types could often have a different (typically shorter) error message. +Technically, lazy out-of-order checking could cause diagnostics to report differently between two locations in an editor - even before this feature - but we didn't want to exacerbate the issue. +With recent work, we've ironed out many of these error inconsistencies. + +Currently, this functionality is available in Visual Studio Code for TypeScript 5.6 and later. + +For more detailed information, [take a look at the implementation and write-up here](https://github.com/microsoft/TypeScript/pull/57842). + +## Granular Commit Characters + +TypeScript's language service now provides its own *commit characters* for each completion item. +Commit characters are specific characters that, when typed, will automatically commit the currently-suggested completion item. + +What this means is that over time your editor will now more frequently commit to the currently-suggested completion item when you type certain characters. +For example, take the following code: + +```ts +declare let food: { + eat(): any; +} + +let f = (foo/**/ +``` + +If our cursor is at `/**/`, it's unclear if the code we're writing is going to be something like `let f = (food.eat())` or `let f = (foo, bar) => foo + bar`. +You could imagine that the editor might be able to auto-complete differently depending on which character we type out next. +For instance, if we type in the period/dot character (`.`), we probably want the editor to complete with the variable `food`; +but if we type the comma character (`,`), we might be writing out a parameter in an arrow function. + +Unfortunately, previously TypeScript just signaled to editors that the current text might define a new parameter name so that *no* commit characters were safe. +So hitting a `.` wouldn't do anything even if it was "obvious" that the editor should auto-complete with the word `food`. + +TypeScript now explicitly lists which characters are safe to commit for each completion item. +While this won't *immediately* change your day-to-day experience, editors that support these commit characters should see behavioral improvements over time. +To see those improvements right now, you can now [use the TypeScript nightly extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.vscode-typescript-next) with [Visual Studio Code Insiders](https://code.visualstudio.com/insiders/). +Hitting `.` in the code above correctly auto-completes with `food`. + +For more information, see [the pull request that added commit characters](https://github.com/microsoft/TypeScript/pull/59339) along with our [adjustments to commit characters depending on context](https://github.com/microsoft/TypeScript/pull/59523). + +## Exclude Patterns for Auto-Imports + +TypeScript's language service now allows you to specify a list of regular expression patterns which will filter away auto-import suggestions from certain specifiers. +For example, if you want to exclude all "deep" imports from a package like `lodash`, you could configure the following preference in Visual Studio Code: + +```json5 +{ + "typescript.preferences.autoImportSpecifierExcludeRegexes": [ + "^lodash/.*$" + ] +} +``` + +Or going the other way, you might want to disallow importing from the entry-point of a package: + +```json5 +{ + "typescript.preferences.autoImportSpecifierExcludeRegexes": [ + "^lodash$" + ] +} +``` + +One could even avoid `node:` imports by using the following setting: + +```json5 +{ + "typescript.preferences.autoImportSpecifierExcludeRegexes": [ + "^node:" + ] +} +``` + +Note that if you want to specify certain flags like `i` or `u`, you will need to surround your regular expression with slashes. +When providing surrounding slashes, you'll need to escape other inner slashes. + +```json5 +{ + "typescript.preferences.autoImportSpecifierExcludeRegexes": [ + "^./lib/internal", // no escaping needed + "/^.\\/lib\\/internal/", // escaping needed - note the leading and trailing slashes + "/^.\\/lib\\/internal/i" // escaping needed - we needed slashes to provide the 'i' regex flag + ] +} +``` + +In Visual Studio Code, the same settings can be applied for JavaScript through `javascript.preferences.autoImportSpecifierExcludeRegexes`. + +For more information, [see the implementation here](https://github.com/microsoft/TypeScript/pull/59543). + +## Notable Behavioral Changes + +This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. +Sometimes it will highlight deprecations, removals, and new restrictions. +It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors. + +### `lib.d.ts` + +Types generated for the DOM may have an impact on type-checking your codebase. +For more information, [see linked issues related to DOM and `lib.d.ts` updates for this version of TypeScript](https://github.com/microsoft/TypeScript/issues/58764). + +### `.tsbuildinfo` is Always Written + +To enable `--build` to continue building projects even if there are intermediate errors in dependencies, and to support `--noCheck` on the command line, TypeScript now always emits a `.tsbuildinfo` file for any project in a `--build` invocation. +This happens regardless of whether `--incremental` is actually on. +[See more information here](https://github.com/microsoft/TypeScript/pull/58626). + +### Respecting File Extensions and `package.json` from within `node_modules` + +Before Node.js implemented support for ECMAScript modules in v12, there was never a good way for TypeScript to know whether `.d.ts` files it found in `node_modules` represented JavaScript files authored as CommonJS or ECMAScript modules. +When the vast majority of npm was CommonJS-only, this didn't cause many problems - if in doubt, TypeScript could just assume that everything behaved like CommonJS. +Unfortunately, if that assumption was wrong it could allow unsafe imports: + +```ts +// node_modules/dep/index.d.ts +export declare function doSomething(): void; + +// index.ts +// Okay if "dep" is a CommonJS module, but fails if +// it's an ECMAScript module - even in bundlers! +import dep from "dep"; +dep.doSomething(); +``` + +In practice, this didn't come up very often. +But in the years since Node.js started supporting ECMAScript modules, the share of ESM on npm has grown. +Fortunately, Node.js also introduced a mechanism that can help TypeScript determine if a file is an ECMAScript module or a CommonJS module: the `.mjs` and `.cjs` file extensions and the `package.json` `"type"` field. +TypeScript 4.7 added support for understanding these indicators, as well as authoring `.mts` and `.cts` files; +however, TypeScript would *only* read those indicators under `--module node16` and `--module nodenext`, so the unsafe import above was still a problem for anyone using `--module esnext` and `--moduleResolution bundler`, for example. + +To solve this, TypeScript 5.6 collects module format information and uses it to resolve ambiguities like the one in the example above in _all_ `module` modes (except `amd`, `umd`, and `system`). +Format-specific file extensions (`.mts` and `.cts`) are respected anywhere they're found, and the `package.json` `"type"` field is consulted inside `node_modules` dependencies, regardless of the `module` setting. +Previously, it was technically possible to produce CommonJS output into a `.mjs` file or vice versa: + +```ts +// main.mts +export default "oops"; + +// $ tsc --module commonjs main.mts +// main.mjs +Object.defineProperty(exports, "__esModule", { value: true }); +exports.default = "oops"; +``` + +Now, `.mts` files never emit CommonJS output, and `.cts` files never emit ESM output. + +Note that much of this behavior was provided in pre-release versions of TypeScript 5.5 ([implementation details here](https://github.com/microsoft/TypeScript/pull/57896)), but in 5.6 this behavior is only extended to files within `node_modules`. + +More details are available [on the change here](https://github.com/microsoft/TypeScript/pull/58825). + +### Correct `override` Checks on Computed Properties + +Previously, computed properties marked with `override` did not correctly check for the existence of a base class member. +Similarly, if you used `noImplicitOverride`, you would not get an error if you *forgot* to add an `override` modifier to a computed property. + +TypeScript 5.6 now correctly checks computed properties in both cases. + +```ts +const foo = Symbol("foo"); +const bar = Symbol("bar"); + +class Base { + [bar]() {} +} + +class Derived extends Base { + override [foo]() {} +// ~~~~~ +// error: This member cannot have an 'override' modifier because it is not declared in the base class 'Base'. + + [bar]() {} +// ~~~~~ +// error under noImplicitOverride: This member must have an 'override' modifier because it overrides a member in the base class 'Base'. +} +``` + +This fix was contributed thanks to [Oleksandr Tarasiuk](https://github.com/a-tarasyuk) in [this pull request](https://github.com/microsoft/TypeScript/pull/57146). From cc082a01a15f8269d8f6a0744bf59544af44b91a Mon Sep 17 00:00:00 2001 From: navya9singh Date: Wed, 29 Jan 2025 12:11:17 -0800 Subject: [PATCH 2/3] 5.7 release notes --- .../copy/en/release-notes/TypeScript 5.7.md | 495 ++++++++++++++++++ 1 file changed, 495 insertions(+) create mode 100644 packages/documentation/copy/en/release-notes/TypeScript 5.7.md diff --git a/packages/documentation/copy/en/release-notes/TypeScript 5.7.md b/packages/documentation/copy/en/release-notes/TypeScript 5.7.md new file mode 100644 index 000000000000..95219e2afa26 --- /dev/null +++ b/packages/documentation/copy/en/release-notes/TypeScript 5.7.md @@ -0,0 +1,495 @@ +--- +title: TypeScript 5.7 +layout: docs +permalink: /docs/handbook/release-notes/typescript-5-7.html +oneline: TypeScript 5.7 Release Notes +--- + +## Checks for Never-Initialized Variables + +For a long time, TypeScript has been able to catch issues when a variable has not yet been initialized in all prior branches. + +```ts +let result: number +if (someCondition()) { + result = doSomeWork(); +} +else { + let temporaryWork = doSomeWork(); + temporaryWork *= 2; + // forgot to assign to 'result' +} + +console.log(result); // error: Variable 'result' is used before being assigned. +``` + +Unfortunately, there are some places where this analysis doesn't work. +For example, if the variable is accessed in a separate function, the type system doesn't know when the function will be called, and instead takes an "optimistic" view that the variable will be initialized. + +```ts +function foo() { + let result: number + if (someCondition()) { + result = doSomeWork(); + } + else { + let temporaryWork = doSomeWork(); + temporaryWork *= 2; + // forgot to assign to 'result' + } + + printResult(); + + function printResult() { + console.log(result); // no error here. + } +} +``` + +While TypeScript 5.7 is still lenient on variables that have *possibly* been initialized, the type system is able to report errors when variables have *never* been initialized at all. + +```ts +function foo() { + let result: number + + // do work, but forget to assign to 'result' + + function printResult() { + console.log(result); // error: Variable 'result' is used before being assigned. + } +} +``` + +[This change](https://github.com/microsoft/TypeScript/pull/55887) was contributed thanks to the work of GitHub user [Zzzen](https://github.com/Zzzen)! + +## Path Rewriting for Relative Paths + +There are several tools and runtimes that allow you to run TypeScript code "in-place", meaning they do not require a build step which generates output JavaScript files. +For example, ts-node, tsx, Deno, and Bun all support running `.ts` files directly. +More recently, Node.js has been investigating such support with `--experimental-strip-types` (soon to be unflagged!) and `--experimental-transform-types`. +This is extremely convenient because it allows us to iterate faster without worrying about re-running a build task. + +There is some complexity to be aware of when using these modes though. +To be maximally compatible with all these tools, a TypeScript file that's imported "in-place" **must** be imported with the appropriate TypeScript extension at runtime. +For example, to import a file called `foo.ts`, we have to write the following in Node's new experimental support: + +```ts +// main.ts + +import * as foo from "./foo.ts"; // <- we need foo.ts here, not foo.js +``` + +Typically, TypeScript would issue an error if we did this, because it expects us to import *the output file*. +Because some tools do allow `.ts` imports, TypeScript has supported this import style with an option called `--allowImportingTsExtensions` for a while now. +This works fine, but what happens if we need to actually generate `.js` files out of these `.ts` files? +This is a requirement for library authors who will need to be able to distribute just `.js` files, but up until now TypeScript has avoided rewriting any paths. + +To support this scenario, we've added a new compiler option called `--rewriteRelativeImportExtensions`. +When an import path is *relative* (starts with `./` or `../`), ends in a TypeScript extension (`.ts`, `.tsx`, `.mts`, `.cts`), and is a non-declaration file, the compiler will rewrite the path to the corresponding JavaScript extension (`.js`, `.jsx`, `.mjs`, `.cjs`). + +```ts +// Under --rewriteRelativeImportExtensions... + +// these will be rewritten. +import * as foo from "./foo.ts"; +import * as bar from "../someFolder/bar.mts"; + +// these will NOT be rewritten in any way. +import * as a from "./foo"; +import * as b from "some-package/file.ts"; +import * as c from "@some-scope/some-package/file.ts"; +import * as d from "#/file.ts"; +import * as e from "./file.js"; +``` + +This allows us to write TypeScript code that can be run in-place and then compiled into JavaScript when we're ready. + +Now, we noted that TypeScript generally avoided rewriting paths. +There are several reasons for this, but the most obvious one is dynamic imports. +If a developer writes the following, it's not trivial to handle the path that `import` receives. +In fact, it's impossible to override the behavior of `import` within any dependencies. + +```ts +function getPath() { + if (Math.random() < 0.5) { + return "./foo.ts"; + } + else { + return "./foo.js"; + } +} + +let myImport = await import(getPath()); +``` + +Another issue is that (as we saw above) only *relative* paths are rewritten, and they are written "naively". +This means that any path that relies on TypeScript's `baseUrl` and `paths` will not get rewritten: + +```json5 +// tsconfig.json + +{ + "compilerOptions": { + "module": "nodenext", + // ... + "paths": { + "@/*": ["./src/*"] + } + } +} +``` + +```ts +// Won't be transformed, won't work. +import * as utilities from "@/utilities.ts"; +``` + +Nor will any path that might resolve through the [`exports`](https://nodejs.org/api/packages.html#exports) and [`imports`](https://nodejs.org/api/packages.html#imports) fields of a `package.json`. + +```json5 +// package.json +{ + "name": "my-package", + "imports": { + "#root/*": "./dist/*" + } +} +``` + +```ts +// Won't be transformed, won't work. +import * as utilities from "#root/utilities.ts"; +``` + +As a result, if you've been using a workspace-style layout with multiple packages referencing each other, you might need to use [conditional exports](https://nodejs.org/api/packages.html#conditional-exports) with [scoped custom conditions](https://nodejs.org/api/packages.html#resolving-user-conditions) to make this work: + +```json5 +// my-package/package.json + +{ + "name": "my-package", + "exports": { + ".": { + "@my-package/development": "./src/index.ts", + "import": "./lib/index.js" + }, + "./*": { + "@my-package/development": "./src/*.ts", + "import": "./lib/*.js" + } + } +} +``` + +Any time you want to import the `.ts` files, you can run it with `node --conditions=@my-package/development`. + +Note the "namespace" or "scope" we used for the condition `@my-package/development`. +This is a bit of a makeshift solution to avoid conflicts from dependencies that might also use the `development` condition. +If everyone ships a `development` in their package, then resolution may try to resolve to a `.ts` file which will not necessarily work. +This idea is similar to what's described in Colin McDonnell's essay *[Live types in a TypeScript monorepo](https://colinhacks.com/essays/live-types-typescript-monorepo#:~:text=custom%20conditions)*, along with [tshy's guidance for loading from source](https://github.com/isaacs/tshy#loading-from-source). + +For more specifics on how this feature works, [read up on the change here](https://github.com/microsoft/TypeScript/pull/59767). + +## Support for `--target es2024` and `--lib es2024` + +TypeScript 5.7 now supports `--target es2024`, which allows users to target ECMAScript 2024 runtimes. +This target primarily enables specifying the new `--lib es2024` which contains many features for `SharedArrayBuffer` and `ArrayBuffer`, `Object.groupBy`, `Map.groupBy`, `Promise.withResolvers`, and more. +It also moves `Atomics.waitAsync` from `--lib es2022` to `--lib es2024`. + +Note that as part of the changes to `SharedArrayBuffer` and `ArrayBuffer`, the two now diverge a bit. +To bridge the gap and preserve the underlying buffer type, all `TypedArrays` (like `Uint8Array` and others) [are now also generic](https://github.com/microsoft/TypeScript/pull/59417). + +```ts +interface Uint8Array { + // ... +} +``` + +Each `TypedArray` now contains a type parameter named `TArrayBuffer`, though that type parameter has a default type argument so that we can continue to refer to `Int32Array` without explicitly writing out `Int32Array`. + +If you encounter any issues as part of this update, you may need to update `@types/node`. + +[This work](https://github.com/microsoft/TypeScript/pull/58573) was primarily provided thanks to [Kenta Moriuchi](https://github.com/petamoriken)! + +## Searching Ancestor Configuration Files for Project Ownership + +When a TypeScript file is loaded in an editor using TSServer (like Visual Studio or VS Code), the editor will try to find the relevant `tsconfig.json` file that "owns" the file. +To do this, it walks up the directory tree from the file being edited, looking for any file named `tsconfig.json`. + +Previously, this search would stop at the first `tsconfig.json` file found; +however, imagine a project structure like the following: + +``` +project/ +├── src/ +│ ├── foo.ts +│ ├── foo-test.ts +│ ├── tsconfig.json +│ └── tsconfig.test.json +└── tsconfig.json +``` + +Here, the idea is that `src/tsconfig.json` is the "main" configuration file for the project, and `src/tsconfig.test.json` is a configuration file for running tests. + +```json5 +// src/tsconfig.json +{ + "compilerOptions": { + "outDir": "../dist" + }, + "exclude": ["**/*.test.ts"] +} +``` + +```json5 +// src/tsconfig.test.json +{ + "compilerOptions": { + "outDir": "../dist/test" + }, + "include": ["**/*.test.ts"], + "references": [ + { "path": "./tsconfig.json" } + ] +} +``` + +```json5 +// tsconfig.json +{ + // This is a "workspace-style" or "solution-style" tsconfig. + // Instead of specifying any files, it just references all the actual projects. + "files": [], + "references": [ + { "path": "./src/tsconfig.json" }, + { "path": "./src/tsconfig.test.json" }, + ] +} +``` + +The problem here is that when editing `foo-test.ts`, the editor would find `project/src/tsconfig.json` as the "owning" configuration file - but that's not the one we want! +If the walk stops at this point, that might not be desirable. +The only way to avoid this previously was to rename `src/tsconfig.json` to something like `src/tsconfig.src.json`, and then all files would hit the top-level `tsconfig.json` which references every possible project. + +``` +project/ +├── src/ +│ ├── foo.ts +│ ├── foo-test.ts +│ ├── tsconfig.src.json +│ └── tsconfig.test.json +└── tsconfig.json +``` + +Instead of forcing developers to do this, TypeScript 5.7 now continues walking up the directory tree to find other appropriate `tsconfig.json` files for editor scenarios. +This can provide more flexibility in how projects are organized and how configuration files are structured. + +You can get more specifics on the implementation on GitHub [here](https://github.com/microsoft/TypeScript/pull/57196) and [here](https://github.com/microsoft/TypeScript/pull/59688). + +## Faster Project Ownership Checks in Editors for Composite Projects + +Imagine a large codebase with the following structure: + +``` +packages +├── graphics/ +│ ├── tsconfig.json +│ └── src/ +│ └── ... +├── sound/ +│ ├── tsconfig.json +│ └── src/ +│ └── ... +├── networking/ +│ ├── tsconfig.json +│ └── src/ +│ └── ... +├── input/ +│ ├── tsconfig.json +│ └── src/ +│ └── ... +└── app/ + ├── tsconfig.json + ├── some-script.js + └── src/ + └── ... +``` + +Each directory in `packages` is a separate TypeScript project, and the `app` directory is the main project that depends on all the other projects. + +```json5 +// app/tsconfig.json +{ + "compilerOptions": { + // ... + }, + "include": ["src"], + "references": [ + { "path": "../graphics/tsconfig.json" }, + { "path": "../sound/tsconfig.json" }, + { "path": "../networking/tsconfig.json" }, + { "path": "../input/tsconfig.json" } + ] +} +``` + +Now notice we have the file `some-script.js` in the `app` directory. +When we open `some-script.js` in the editor, the TypeScript language service (which also handles the editor experience for JavaScript files!) has to figure out which project the file belongs to so it can apply the right settings. + +In this case, the nearest `tsconfig.json` does *not* include `some-script.js`, but TypeScript will proceed to ask "could one of the projects *referenced* by `app/tsconfig.json` include `some-script.js`?". +To do so, TypeScript would previously load up each project, one-by-one, and stop as soon as it found a project which contained `some-script.js`. +Even if `some-script.js` isn't included in the root set of files, TypeScript would still parse all the files within a project because some of the root set of files can still *transitively* reference `some-script.js`. + +What we found over time was that this behavior caused extreme and unpredictable behavior in larger codebases. +Developers would open up stray script files and find themselves waiting for their entire codebase to be opened up. + +Thankfully, every project that can be referenced by another (non-workspace) project must enable a flag called `composite`, which enforces a rule that all input source files must be known up-front. +So when probing a `composite` project, TypeScript 5.7 will only check if a file belongs to the *root set of files* of that project. +This should avoid this common worst-case behavior. + +For more information, [see the change here](https://github.com/microsoft/TypeScript/pull/59688). + +### Validated JSON Imports in `--module nodenext` + +When importing from a `.json` file under `--module nodenext`, TypeScript will now enforce certain rules to prevent runtime errors. + +For one, an import attribute containing `type: "json"` needs to be present for any JSON file import. + +```ts +import myConfig from "./myConfig.json"; +// ~~~~~~~~~~~~~~~~~ +// ❌ error: Importing a JSON file into an ECMAScript module requires a 'type: "json"' import attribute when 'module' is set to 'NodeNext'. + +import myConfig from "./myConfig.json" with { type: "json" }; +// ^^^^^^^^^^^^^^^^ +// ✅ This is fine because we provided `type: "json"` +``` + +On top of this validation, TypeScript will not generate "named" exports, and the contents of a JSON import will only be accessible via a default. + +```ts +// ✅ This is okay: +import myConfigA from "./myConfig.json" with { type: "json" }; +let version = myConfigA.version; + +/////////// + +import * as myConfigB from "./myConfig.json" with { type: "json" }; + +// ❌ This is not: +let version = myConfig.version; + +// ✅ This is okay: +let version = myConfig.default.version; +``` + +[See here](https://github.com/microsoft/TypeScript/pull/60019) for more information on this change. + +## Support for V8 Compile Caching in Node.js + +Node.js 22 supports [a new API called `module.enableCompileCache()`](https://github.com/nodejs/node/pull/54501). +This API allows the runtime to reuse some of the parsing and compilation work done after the first run of a tool. + +TypeScript 5.7 now leverages the API so that it can start doing useful work sooner. +In some of our own testing, we've witnessed about a 2.5x speed-up in running `tsc --version`. + +``` +Benchmark 1: node ./built/local/_tsc.js --version (*without* caching) + Time (mean ± σ): 122.2 ms ± 1.5 ms [User: 101.7 ms, System: 13.0 ms] + Range (min … max): 119.3 ms … 132.3 ms 200 runs + +Benchmark 2: node ./built/local/tsc.js --version (*with* caching) + Time (mean ± σ): 48.4 ms ± 1.0 ms [User: 34.0 ms, System: 11.1 ms] + Range (min … max): 45.7 ms … 52.8 ms 200 runs + +Summary + node ./built/local/tsc.js --version ran + 2.52 ± 0.06 times faster than node ./built/local/_tsc.js --version +``` + +For more information, [see the pull request here](https://github.com/microsoft/TypeScript/pull/59720). + +## Notable Behavioral Changes + +This section highlights a set of noteworthy changes that should be acknowledged and understood as part of any upgrade. +Sometimes it will highlight deprecations, removals, and new restrictions. +It can also contain bug fixes that are functionally improvements, but which can also affect an existing build by introducing new errors. + +### `lib.d.ts` + +Types generated for the DOM may have an impact on type-checking your codebase. +For more information, [see linked issues related to DOM and `lib.d.ts` updates for this version of TypeScript](https://github.com/microsoft/TypeScript/pull/60061). + +### `TypedArray`s Are Now Generic Over `ArrayBufferLike` + +In ECMAScript 2024, `SharedArrayBuffer` and `ArrayBuffer` have types that slightly diverge. +To bridge the gap and preserve the underlying buffer type, all `TypedArrays` (like `Uint8Array` and others) [are now also generic](https://github.com/microsoft/TypeScript/pull/59417). + +```ts +interface Uint8Array { + // ... +} +``` + +Each `TypedArray` now contains a type parameter named `TArrayBuffer`, though that type parameter has a default type argument so that users can continue to refer to `Int32Array` without explicitly writing out `Int32Array`. + +If you encounter any issues as part of this update, such as + +``` +error TS2322: Type 'Buffer' is not assignable to type 'Uint8Array'. +error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'Uint8Array'. +error TS2345: Argument of type 'ArrayBufferLike' is not assignable to parameter of type 'ArrayBuffer'. +error TS2345: Argument of type 'Buffer' is not assignable to parameter of type 'string | ArrayBufferView | Stream | Iterable | AsyncIterable'. +``` + +then you may need to update `@types/node`. + +You can read the [specifics about this change on GitHub](https://github.com/microsoft/TypeScript/pull/59417). + +### Creating Index Signatures from Non-Literal Method Names in Classes + +TypeScript now has a more consistent behavior for methods in classes when they are declared with non-literal computed property names. +For example, in the following: + +```ts +declare const symbolMethodName: symbol; + +export class A { + [symbolMethodName]() { return 1 }; +} +``` + +Previously TypeScript just viewed the class in a way like the following: + +```ts +export class A { +} +``` + +In other words, from the type system's perspective, `[symbolMethodName]` contributed nothing to the type of `A` + +TypeScript 5.7 now views the method `[symbolMethodName]() {}` more meaningfully, and generates an index signature. +As a result, the code above is interpreted as something like the following code: + +```ts +export class A { + [x: symbol]: () => number; +} +``` + +This provides behavior that is consistent with properties and methods in object literals. + +[Read up more on this change here](https://github.com/microsoft/TypeScript/pull/59860). + +### More Implicit `any` Errors on Functions Returning `null` and `undefined` + +When a function expression is contextually typed by a signature returning a generic type, TypeScript now appropriately provides an implicit `any` error under `noImplicitAny`, but outside of `strictNullChecks`. + +```ts +declare var p: Promise; +const p2 = p.catch(() => null); +// ~~~~~~~~~~ +// error TS7011: Function expression, which lacks return-type annotation, implicitly has an 'any' return type. +``` + +[See this change for more details](https://github.com/microsoft/TypeScript/pull/59661). From 3f31babeb12fd4511ad2112704a388bcfa832e10 Mon Sep 17 00:00:00 2001 From: navya9singh Date: Wed, 29 Jan 2025 12:44:20 -0800 Subject: [PATCH 3/3] adding filenames --- packages/documentation/scripts/types/AllFilenames.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/documentation/scripts/types/AllFilenames.d.ts b/packages/documentation/scripts/types/AllFilenames.d.ts index 2497b0ac0f06..0a1e82f17bd2 100644 --- a/packages/documentation/scripts/types/AllFilenames.d.ts +++ b/packages/documentation/scripts/types/AllFilenames.d.ts @@ -128,6 +128,8 @@ export type AllDocsPages = | "release-notes/TypeScript 5.3.md" | "release-notes/TypeScript 5.4.md" | "release-notes/TypeScript 5.5.md" + | "release-notes/TypeScript 5.6.md" + | "release-notes/TypeScript 5.7.md" | "tutorials/ASP.NET Core.md" | "tutorials/Angular.md" | "tutorials/Babel with TypeScript.md"