From e32fb0d859c047837e7a1c47081a47d51378e15e Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 19 Sep 2024 20:29:56 -0400 Subject: [PATCH 1/9] docs: add 'Typed Linting' blog post --- .cspell.json | 1 + docs/getting-started/Typed_Linting.mdx | 2 + .../website/blog/2024-09-30-typed-linting.md | 247 ++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 packages/website/blog/2024-09-30-typed-linting.md diff --git a/.cspell.json b/.cspell.json index 93e100a70adf..deb654ab3735 100644 --- a/.cspell.json +++ b/.cspell.json @@ -83,6 +83,7 @@ "Cheung", "codebases", "Codecov", + "codemod", "contravariant", "Crockford", "declarators", diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index 2c76b9dfff73..d5da84d672db 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -16,6 +16,8 @@ To tap into TypeScript's additional powers, there are two small changes you need 2. Add `languageOptions.parserOptions` to tell our parser how to find the TSConfig for each source file. ```js title="eslint.config.mjs" +import tseslint from 'typescript-eslint'; + export default tseslint.config( eslint.configs.recommended, // Remove this line diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md new file mode 100644 index 000000000000..13d033d38f7f --- /dev/null +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -0,0 +1,247 @@ +--- +authors: joshuakgoldberg +description: Explaining what linting with type information means, why it's so powerful, and some of the useful rules you can enable that use it. +slug: typed-linting +tags: [breaking changes, typescript-eslint, v7, v8] +title: 'Typed Linting: The Most Powerful TypeScript Linting Ever' +--- + +[Linting with type information](https://typescript-eslint.io/getting-started/typed-linting), also called "typed linting" or "type-aware linting", is the act of writing lint rules that use type information to understand your code. +Typed linting rules as provided by typescript-eslint are the most powerful JavaScript/TypeScript linting in common use today. + +In this blog post, we'll give a high-level overview of how linting with type information works, why it's so much more powerful than traditional linting, and some of the useful rules you can enable that use it. + + + +## Recap: Type Information? + +Traditional lint rules operate on one file at a time. +They look at a description of code in each file and report complaints if that file seems to contain bad practices. +That description is called an Abstract Syntax Tree, or AST. + +:::tip +For a primer on ASTs and linting, see _[ASTs and typescript-eslint](./2022-12-05-asts-and-typescript-eslint.md)_. +::: + +Each file's AST contains only information for that file, not any other files. +Lint rules that rely only on the file's AST don't have a way to understand code imported from other files, such as in ESM `import` statements. +Not being able to understand code from other files severely limits lint rules. + +As an example, suppose you enable a lint rule like [`@typescript-eslint/no-deprecated`](/rules/no-deprecated) to prevent calling to code with a `@deprecated` JSDoc. +Using just the following `index.ts` file's AST, the lint rule would have no way of knowing whether `work` is deprecated: + +```ts title="index.ts" +import { work } from './worker'; + +// Is this safe? Does calling work violate any rules? We don't know! +work(); +``` + +_Type information_ refers to the cross-file information that a type checker such as TypeScript can generate to understand your code. +TypeScript and tools that call to TypeScript's APIs can use that type information to understand the project's code. + +In the earlier example, type information would be able to inform a lint rule running in `index.ts` that the `work` import resolves to a function in another file: + +```ts title="worker.ts" +/** @deprecated - Don't do this! */ +export function work() { + // ... +} +``` + +...which would allow the lint rule to report a complaint that the `work()` call is to a function marked as `@deprecated`. + +typescript-eslint allows lint rules to retrieve type information using TypeScript's APIs. +In doing so, they can make decisions on linted files using information outside each individual file. + +## Common Uses for Typed Linting + +Cross-file type information is a powerful addition to lint rules. +Knowing the types of pieces of your code allows lint rules to flag for risky behavior specific to certain types. +The following sections show several of the most common uses for lint rules that rely on type information. + +### Unsafe `any`s + +The `@typescript-eslint/no-unsafe-*` family of rules checks for risky uses of `any` typed values. +This is useful because the `any` type can easily slip into code and reduce type safety, despite being allowed by the TypeScript type checker. + +For example, the following code that logs a member of an object parsed from a string produces no type errors in type checking. +`JSON.parse()` returns `any`, and arbitrary property accesses are allowed on values of type `any`. + +However, [`@typescript-eslint/no-unsafe-member-access`](/rules/no-unsafe-member-access) would report `[key]` might not be a property on the object: + +```ts +function logDataKey(rawData: string, key: string): string { + return JSON.parse(rawData)[key]; + // ~~~~~ + // Unsafe member access [key] on an `any` value. + // eslint(@typescript-eslint/no-unsafe-member-access) +} +``` + +The lint rule is right to report. +Calls to the `logDataKey` function can return a value that's not a `string`, despite the function's explicit return type annotation. +That can lead to unexpected behavior at runtime: + +```ts +console.log(logDataKey(`{ "blue": "cheese" }`, 'bleu').toUpperCase()); +// Uncaught TypeError: Cannot read properties of undefined (reading 'toUpperCase') +``` + +Without type information to indicate the types of `JSON.parse` and `key`, there would have been no way to determine that the `[key]` member access was unsafe. + +### Method Call Scoping + + + +Runtime crashes caused by misuses of typed code are possible even with no `any`s. + +For example, class method functions don't preserve their class scope when passed as standalone variables ("unbound"). +TypeScript still allows them to be called without the proper `this` scope. + +The global `localStorage` object in browsers has several properties that must be called with a `this` bound to `localStorage`. +The [`@typescript-eslint/unbound-method`](/rules/unbound-method) lint rule can report on unsafe references to those properties, such as accessing `getItem`: + +```ts +const { getItem } = localStorage; +// ~~~~~~~ +// Avoid referencing unbound methods which may cause unintentional scoping of `this`. +// eslint(@typescript-eslint/unbound-method) +``` + +That's useful because calls to `getItem` that aren't bound to `localStorage` cause an exception at runtime: + +```ts +getItem('...'); +// Uncaught TypeError: Illegal invocation +``` + +Without type information to indicate the types of `localStorage` and its `getItem` property, there would have been no way to determine that the `const { getItem }` access was unsafe. + +### Async Race Conditions + +Even if your code is 100% typed, has no `any`s, and doesn't misuse scopes, it's still possible to have bugs that can only easily be detected by typed linting. +Asynchronous code with `Promise`s in particular can introduce subtle issues that are completely type-safe. + +Suppose your code is meant to run an asynchronous `readFromCache` function before reading from the file system: + +```ts +import { fs } from 'node:fs/promises'; +import { readFromCache } from './caching'; + +const filePath = './data.json'; + +readFromCache(filePath);) + +await fs.rm(filePath); +``` + +Do you see the potential bug? + +If `readFromCache` is asynchronous (returns a `Promise`), then calling it and not awaiting its returned Promise could lead to race conditions in code. +Its asynchronous or delayed logic might not get to reading from the `filePath` before `fs.rm(filePath)` runs. + +This is commonly referred to as a _"floating"_ Promise: one that is created but not appropriately handled. +The [`@typescript-eslint/no-floating-promises`](/rules/no-floating-promises) lint rule would report on that floating Promise: + +```ts +readFromCache(filePath); +// Promises must be awaited, end with a call to .catch, end with a call to .then +// with a rejection handler or be explicitly marked as ignored with the `void` operator. +// eslint(@typescript-eslint/no-floating-promises +``` + +...and can give an editor suggestion to add a missing `await`: + +```diff +- readFromCache(filePath); ++ await readFromCache(filePath); +``` + +Determining whether code is creating a floating Promise is only possible when the types of code are known. +Otherwise, lint rules would have no way of knowing which imports from other files could potentially create a Promise that needs to be handled. + +### Custom Rules + +Typed linting isn't restricted to just typescript-eslint rules. +It can be used in community ESLint plugins, as well as custom rules specific to your project. + +One common example used by teams is to codemod from a deprecated API to its replacement. +Typed linting is often necessary to determine which pieces of code call to the old API. + +As an example, consider the following `fetch()` network call. +A typed lint rule could determine that `endpoint` is a string referring to an old endpoint: + +```ts +import { endpoint } from '~/api/v1'; + +await fetch(endpoint, { + // ~~~~~~~~ + // Don't pass the deprecated endpoint to fetch(). + // eslint(@my-team/custom-rule) + // ... +}); +``` + +...and provide a code fix to automatically move to the new endpoint: + +```diff +import { endpoint } from '~/api/v1'; ++ import { endpointv2 } from '~/api/v2'; + +- await fetch(endpoint, { ++ await fetch(endpointV2, { + // ... +}); +``` + +That kind of migration codemod is one of the ways typed linting can be utilized for project- or team-specific rules. +See [Developers > Custom Rules](/developers/custom-rules) for more documentation on building your own ESLint rules with typescript-eslint. + +## Enabling Typed Linting + +You can add typed linting to your ESLint configuration by following the steps in [Linting with Type Information](/getting-started/linting-with-type-information). +We recommend doing so by enabling [`parserOptions.projectService`](/packages/parser#projectservice): + +```js title="eslint.config.js" +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + languageOptions: { + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, +); +``` + +typescript-eslint will then use TypeScript APIs behind-the-scenes, to, for each file being linted: + +1. Determine the appropriate TSConfig dictating how to generate that file's type information +2. Make APIs available to lint rules to retrieve type information for the file + +Lint rules that opt into type information will then be able to use those APIs when linting your code. + +## Drawbacks of Typed Linting + +Linting with type information comes with two drawbacks: configuration complexity and a performance penalty. + +For configuring typed linting, [`parserOptions.projectService`](/packages/parser#projectservice) solves configuration difficulties for most projects. +The more manual [`parserOptions.project`](/packages/parser#projectservice) is also available for more complex project setups. +See [Troubleshooting & FAQs > Typed Linting](/troubleshooting/typed-linting) for details on common issues. + +For performance, it is inevitable that typed linting will slow your linting down to roughly the speed of type checking your project. +Typed lint rules call to the same TypeScript APIs as the command-line `tsc`. +If linting your project is much slower than running `tsc` on the same set of files, see [Troubleshooting & FAQs > Typed Linting > Performance](/troubleshooting/typed-linting/performance). + +## Final Thoughts + +In our experience, the additional bug catching and features added by typed linting are well worth the costs of configuration and performance. +Typed linting allows lint rules to act with much greater confidence on a wider area of checking, including avoiding unsafe `any` uses, enforcing proper `this` scopes, and catching asynchronous code mishaps. + +If you haven't yet tried out typed linting using the typescript-eslint rules mentioned in this blog post, we'd strongly recommend going through our [Linting with Type Information](/getting-started/typed-linting) guide. From 04ce78ed8f50bc1acf725d02173f35f1daa232f0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Thu, 19 Sep 2024 20:37:37 -0400 Subject: [PATCH 2/9] fix: /getting-started/linting-with-type-information --- packages/website/blog/2024-09-30-typed-linting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index 13d033d38f7f..07961fa1cc3d 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -200,7 +200,7 @@ See [Developers > Custom Rules](/developers/custom-rules) for more documentation ## Enabling Typed Linting -You can add typed linting to your ESLint configuration by following the steps in [Linting with Type Information](/getting-started/linting-with-type-information). +You can add typed linting to your ESLint configuration by following the steps in [Linting with Type Information](/getting-started/typed-linting). We recommend doing so by enabling [`parserOptions.projectService`](/packages/parser#projectservice): ```js title="eslint.config.js" From 09d615bb836f0c48f36ca54de4ce4789f270f39a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Fri, 20 Sep 2024 19:19:41 -0400 Subject: [PATCH 3/9] fix tags --- packages/website/blog/2024-09-30-typed-linting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index 07961fa1cc3d..f1971d8d68f2 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -2,7 +2,7 @@ authors: joshuakgoldberg description: Explaining what linting with type information means, why it's so powerful, and some of the useful rules you can enable that use it. slug: typed-linting -tags: [breaking changes, typescript-eslint, v7, v8] +tags: [types, type information, typed linting] title: 'Typed Linting: The Most Powerful TypeScript Linting Ever' --- From 27016694789b13f2bc3c5e3f41921febf98e0906 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 23 Sep 2024 08:09:15 -0400 Subject: [PATCH 4/9] Maybe better explain type information... --- packages/website/blog/2024-09-30-typed-linting.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index f1971d8d68f2..c8bc47dec33b 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -37,8 +37,9 @@ import { work } from './worker'; work(); ``` -_Type information_ refers to the cross-file information that a type checker such as TypeScript can generate to understand your code. -TypeScript and tools that call to TypeScript's APIs can use that type information to understand the project's code. +_Type information_ refers to the information a type checker such as TypeScript generates to understand your code. +Type checkers read code, determine what types each value may be, and store that "type information". +TypeScript and tools that call to TypeScript's APIs can then use that type information to understand the project's code. In the earlier example, type information would be able to inform a lint rule running in `index.ts` that the `work` import resolves to a function in another file: @@ -131,7 +132,7 @@ import { readFromCache } from './caching'; const filePath = './data.json'; -readFromCache(filePath);) +readFromCache(filePath); await fs.rm(filePath); ``` From e1be9f15b32f8ca7185d66eab390cc233de41c33 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 23 Sep 2024 12:54:32 -0400 Subject: [PATCH 5/9] more touchups --- docs/getting-started/Typed_Linting.mdx | 1 + .../website/blog/2024-09-30-typed-linting.md | 34 +++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/docs/getting-started/Typed_Linting.mdx b/docs/getting-started/Typed_Linting.mdx index d5da84d672db..b1e6a9d16663 100644 --- a/docs/getting-started/Typed_Linting.mdx +++ b/docs/getting-started/Typed_Linting.mdx @@ -16,6 +16,7 @@ To tap into TypeScript's additional powers, there are two small changes you need 2. Add `languageOptions.parserOptions` to tell our parser how to find the TSConfig for each source file. ```js title="eslint.config.mjs" +import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; export default tseslint.config( diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index c8bc47dec33b..b324647374c6 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -72,7 +72,7 @@ For example, the following code that logs a member of an object parsed from a st However, [`@typescript-eslint/no-unsafe-member-access`](/rules/no-unsafe-member-access) would report `[key]` might not be a property on the object: ```ts -function logDataKey(rawData: string, key: string): string { +function getDataKey(rawData: string, key: string): string { return JSON.parse(rawData)[key]; // ~~~~~ // Unsafe member access [key] on an `any` value. @@ -81,11 +81,11 @@ function logDataKey(rawData: string, key: string): string { ``` The lint rule is right to report. -Calls to the `logDataKey` function can return a value that's not a `string`, despite the function's explicit return type annotation. +Calls to the `getDataKey` function can return a value that's not a `string`, despite the function's explicit return type annotation. That can lead to unexpected behavior at runtime: ```ts -console.log(logDataKey(`{ "blue": "cheese" }`, 'bleu').toUpperCase()); +console.log(getDataKey(`{ "blue": "cheese" }`, 'bleu').toUpperCase()); // Uncaught TypeError: Cannot read properties of undefined (reading 'toUpperCase') ``` @@ -170,32 +170,38 @@ It can be used in community ESLint plugins, as well as custom rules specific to One common example used by teams is to codemod from a deprecated API to its replacement. Typed linting is often necessary to determine which pieces of code call to the old API. -As an example, consider the following `fetch()` network call. -A typed lint rule could determine that `endpoint` is a string referring to an old endpoint: +As an example, consider the following `fetch()` POST call that sends data to an intake API in an old format. +Suppose the intake endpoint is migrating from sending string arrays to sending key-value pairs. +A typed lint rule could determine that the data is in the old format: ```ts -import { endpoint } from '~/api/v1'; +import { endpoints } from "~/api"; -await fetch(endpoint, { - // ~~~~~~~~ - // Don't pass the deprecated endpoint to fetch(). +await fetch(endpoints.intake, { + data: JSON.stringify(["key", "value"]) + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // Don't pass an array to endpoints.intake. Pass a key-value object instead. // eslint(@my-team/custom-rule) // ... + method: "POST", }); ``` -...and provide a code fix to automatically move to the new endpoint: +...and provide a code fix to automatically migrate to the new format: ```diff -import { endpoint } from '~/api/v1'; -+ import { endpointv2 } from '~/api/v2'; +import { endpoints } from "~/api"; -- await fetch(endpoint, { -+ await fetch(endpointV2, { +await fetch(endpoints.intake, { +- data: JSON.stringify(["key", "value"]) ++ data: JSON.stringify({ key: value }) // ... + method: "POST", }); ``` +Knowing that the `fetch()` call was being sent to `endpoints.intake` and that the type of the data was a `string[]` instead of `Record` takes typed linting. + That kind of migration codemod is one of the ways typed linting can be utilized for project- or team-specific rules. See [Developers > Custom Rules](/developers/custom-rules) for more documentation on building your own ESLint rules with typescript-eslint. From dfdb1a532e5875618acd4da97fd894c08434e542 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 23 Sep 2024 12:56:13 -0400 Subject: [PATCH 6/9] tuples --- .../website/blog/2024-09-30-typed-linting.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index b324647374c6..2828caa0b541 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -170,17 +170,19 @@ It can be used in community ESLint plugins, as well as custom rules specific to One common example used by teams is to codemod from a deprecated API to its replacement. Typed linting is often necessary to determine which pieces of code call to the old API. -As an example, consider the following `fetch()` POST call that sends data to an intake API in an old format. -Suppose the intake endpoint is migrating from sending string arrays to sending key-value pairs. +As an example, consider the following `fetch()` POST call that sends data to an intake API. +Suppose the intake endpoint is migrating from sending `[string, string]` tuples to sending key-value pairs. A typed lint rule could determine that the data is in the old format: ```ts import { endpoints } from "~/api"; +const rawData = ["key", "value"] as const; + await fetch(endpoints.intake, { - data: JSON.stringify(["key", "value"]) - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Don't pass an array to endpoints.intake. Pass a key-value object instead. + data: JSON.stringify(rawData) + // ~~~~~~~ + // Don't pass a tuple to endpoints.intake. Pass a key-value object instead. // eslint(@my-team/custom-rule) // ... method: "POST", @@ -192,15 +194,17 @@ await fetch(endpoints.intake, { ```diff import { endpoints } from "~/api"; +const rawData = ["key", "value"] as const; + await fetch(endpoints.intake, { -- data: JSON.stringify(["key", "value"]) -+ data: JSON.stringify({ key: value }) +- data: JSON.stringify(rawData) ++ data: JSON.stringify(Object.fromEntries(rawData)) // ... method: "POST", }); ``` -Knowing that the `fetch()` call was being sent to `endpoints.intake` and that the type of the data was a `string[]` instead of `Record` takes typed linting. +Knowing that the `fetch()` call was being sent to `endpoints.intake` and that the type of the data was a tuple takes typed linting. That kind of migration codemod is one of the ways typed linting can be utilized for project- or team-specific rules. See [Developers > Custom Rules](/developers/custom-rules) for more documentation on building your own ESLint rules with typescript-eslint. From ca79a7942cae82db8d3f6673a310611a202f9e5f Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 30 Sep 2024 09:09:42 -0400 Subject: [PATCH 7/9] nit: traditional 'JS' rules --- packages/website/blog/2024-09-30-typed-linting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index 2828caa0b541..dc0a735a05c7 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -15,7 +15,7 @@ In this blog post, we'll give a high-level overview of how linting with type inf ## Recap: Type Information? -Traditional lint rules operate on one file at a time. +Traditional JavaScript lint rules operate on one file at a time. They look at a description of code in each file and report complaints if that file seems to contain bad practices. That description is called an Abstract Syntax Tree, or AST. From 88f153d48378ce2f378f324951c38ea443b6d4bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 30 Sep 2024 11:48:44 -0400 Subject: [PATCH 8/9] Apply suggestions from code review Co-authored-by: Kirk Waiblinger --- packages/website/blog/2024-09-30-typed-linting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index dc0a735a05c7..aeb23256809e 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -28,7 +28,7 @@ Lint rules that rely only on the file's AST don't have a way to understand code Not being able to understand code from other files severely limits lint rules. As an example, suppose you enable a lint rule like [`@typescript-eslint/no-deprecated`](/rules/no-deprecated) to prevent calling to code with a `@deprecated` JSDoc. -Using just the following `index.ts` file's AST, the lint rule would have no way of knowing whether `work` is deprecated: +Using just the following file's AST, the lint rule would have no way of knowing whether `work` is deprecated: ```ts title="index.ts" import { work } from './worker'; @@ -53,7 +53,7 @@ export function work() { ...which would allow the lint rule to report a complaint that the `work()` call is to a function marked as `@deprecated`. typescript-eslint allows lint rules to retrieve type information using TypeScript's APIs. -In doing so, they can make decisions on linted files using information outside each individual file. +In doing so, they can even make decisions on linted files using information outside each individual file. ## Common Uses for Typed Linting From 3f294222030fb557781f362b1cfdbba12f3437b0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 30 Sep 2024 11:49:33 -0400 Subject: [PATCH 9/9] mention: reliable --- packages/website/blog/2024-09-30-typed-linting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/website/blog/2024-09-30-typed-linting.md b/packages/website/blog/2024-09-30-typed-linting.md index aeb23256809e..4037d6053ecb 100644 --- a/packages/website/blog/2024-09-30-typed-linting.md +++ b/packages/website/blog/2024-09-30-typed-linting.md @@ -117,7 +117,7 @@ getItem('...'); // Uncaught TypeError: Illegal invocation ``` -Without type information to indicate the types of `localStorage` and its `getItem` property, there would have been no way to determine that the `const { getItem }` access was unsafe. +Without type information to indicate the types of `localStorage` and its `getItem` property, there would have been no reliable way to determine that the `const { getItem }` access was unsafe. ### Async Race Conditions