diff --git a/.cspell.json b/.cspell.json index edd2532abf92..21ca6f8fc237 100644 --- a/.cspell.json +++ b/.cspell.json @@ -72,6 +72,7 @@ "esquery", "esrecurse", "estree", + "globby", "IDE's", "IIFE", "IIFEs", @@ -121,6 +122,7 @@ "unoptimized", "unprefixed", "upsert", + "warnonunsupportedtypescriptversion", "Zacher" ], "overrides": [ diff --git a/.eslintrc.js b/.eslintrc.js index 7380b874d88f..99f3f5d3e41d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -40,6 +40,11 @@ module.exports = { tsconfigRootDir: __dirname, warnOnUnsupportedTypeScriptVersion: false, EXPERIMENTAL_useSourceOfProjectReferenceRedirect: false, + cacheLifetime: { + // we pretty well never create/change tsconfig structure - so need to ever evict the cache + // in the rare case that we do - just need to manually restart their IDE. + glob: 'Infinity', + }, }, rules: { // make sure we're not leveraging any deprecated APIs @@ -106,6 +111,13 @@ module.exports = { // curly: ['error', 'all'], + eqeqeq: [ + 'error', + 'always', + { + null: 'never', + }, + ], 'no-mixed-operators': 'error', 'no-console': 'error', 'no-process-exit': 'error', @@ -186,6 +198,8 @@ module.exports = { // enforce a sort order across the codebase 'simple-import-sort/imports': 'error', + + 'one-var': ['error', 'never'], }, overrides: [ // all test files diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 3e72e13f7a95..b50374076204 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -8,7 +8,7 @@ Otherwise we may not be able to review your PR. - [ ] Addresses an existing open issue: fixes #000 - [ ] That issue was marked as [accepting prs](https://github.com/typescript-eslint/typescript-eslint/issues?q=is%3Aopen+is%3Aissue+label%3A%22accepting+prs%22) -- [ ] Steps in [CONTRIBUTING.md](https://github.com/typescript-eslint/typescript-eslint/blob/main/CONTRIBUTING.md) were taken +- [ ] Steps in [Contributing](https://typescript-eslint.io/contributing) were taken ## Overview diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a3384f1eaa1..def7977921ba 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -144,7 +144,7 @@ jobs: - name: Install uses: ./.github/actions/prepare-install with: - node-version: ${{ env.PRIMARY_NODE_VERSION }} + node-version: ${{ matrix.node-version }} - name: Build uses: ./.github/actions/prepare-build diff --git a/.gitignore b/.gitignore index 2ce061c3d4d7..0973f04542e0 100644 --- a/.gitignore +++ b/.gitignore @@ -69,6 +69,9 @@ jspm_packages/ # Editor-specific metadata folders .vs +# nodejs cpu profiles +*.cpuprofile + .DS_Store .idea dist diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ad61e896afe..951438d13b65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + + +### Bug Fixes + +* **eslint-plugin:** [sort-type-constituents] fixed behavior change ([#6384](https://github.com/typescript-eslint/typescript-eslint/issues/6384)) ([5bf7f7f](https://github.com/typescript-eslint/typescript-eslint/commit/5bf7f7fe48aee61a676dfbe829c2a5e9e44cd552)), closes [#6339](https://github.com/typescript-eslint/typescript-eslint/issues/6339) +* **eslint-plugin:** do not use .at(), Node 14 does not support it ([#6402](https://github.com/typescript-eslint/typescript-eslint/issues/6402)) ([077ed1b](https://github.com/typescript-eslint/typescript-eslint/commit/077ed1b5be844df35b7fba554ddae579b3144787)) + + +### Features + +* **eslint-plugin:** [naming-convention] improve performance by removing unnecessary selectors ([#6376](https://github.com/typescript-eslint/typescript-eslint/issues/6376)) ([3647a1c](https://github.com/typescript-eslint/typescript-eslint/commit/3647a1c1bbcfe6551647632fc2d978fa90881de1)) +* **eslint-plugin:** [no-floating-promises] error on logical expression ([#6356](https://github.com/typescript-eslint/typescript-eslint/issues/6356)) ([f330e06](https://github.com/typescript-eslint/typescript-eslint/commit/f330e0651548d55163ddc3233c90fd3cbe37c9c0)) +* **eslint-plugin:** [no-import-type-side-effects] add rule to warn against runtime side effects with `verbatimModuleSyntax` ([#6394](https://github.com/typescript-eslint/typescript-eslint/issues/6394)) ([b14d3be](https://github.com/typescript-eslint/typescript-eslint/commit/b14d3be0f305d71e0adfc9381e9de993898b2b43)) +* **eslint-plugin:** [strict-boolean-expressions] add allow nullable enum to strict boolean expressions ([#6096](https://github.com/typescript-eslint/typescript-eslint/issues/6096)) ([d4747cd](https://github.com/typescript-eslint/typescript-eslint/commit/d4747cd8cc9dad2bf2cb64e1c0e8980ce34d82c7)) +* **typescript-estree:** cache project glob resolution ([#6367](https://github.com/typescript-eslint/typescript-eslint/issues/6367)) ([afae837](https://github.com/typescript-eslint/typescript-eslint/commit/afae8374df64101627808ccfeb5b715c865e910f)) + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index b1c6bfe4f0d3..53748bda2658 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -10,77 +10,54 @@ Thanks goes to these wonderful people:
Brad Zacher

Armano

Josh Goldberg
-
Oleksandr T.
+
Michaël De Boey
-
Michaël De Boey

Reyad Attiyat
-
Gareth Jones

Sosuke Suzuki
+
Gareth Jones

Patricio Trevino
+
Joshua Chen
-
Joshua Chen

YeonJuan

Nicholas C. Zakas

Jed Fox
-
Rafael Santana
- -
Ben Lichtman

Nikita
+ +
Taeheon Kim

Scott O'Hara

Retsam
- -
Kai Cataldo

Rasmus Eneman
-
Rebecca Stevens
-
Toru Nagashima
-
Yosuke Ota
+
Toru Nagashima
+
Yosuke Ota

JounQin

Lucas Azzola

Simen Bekkhus
-
Danny Fritz
-
Ika
+
Danny Fritz
+
Omri Luzon

cherryblossom
-
mackie
+
Mackie Underdown

Bryan Mishkin
-
Kanitkorn Sujautra
-
Omri Luzon
+
Kanitkorn Sujautra
+
Sviatoslav Zaytsev

Zzzen

Anix
-
Daniil Dubrava

Pete Gonzalez
-
ldrick
-
Susisu
+
ldrick

SHIMA RYUHEI
-
Gavin Barron
-
Kevin Partington
-
Lucas Duailibe
- -
Niles Salter
-
Pavel Birukov
-
Shahar "Dawn" Or
-
kmin-jeong
-
koooge
- - -
thomas michael wallace
-
Juan García
-
Daniel Nixon
-
Yasar Siddiqui
-
Yusuke Tanaka
diff --git a/docs/Architecture.md b/docs/Architecture.mdx similarity index 100% rename from docs/Architecture.md rename to docs/Architecture.mdx diff --git a/docs/Custom_Rules.md b/docs/Custom_Rules.mdx similarity index 100% rename from docs/Custom_Rules.md rename to docs/Custom_Rules.mdx diff --git a/docs/Getting_Started.md b/docs/Getting_Started.mdx similarity index 94% rename from docs/Getting_Started.md rename to docs/Getting_Started.mdx index 91c978322f48..cdaf39899a9a 100644 --- a/docs/Getting_Started.md +++ b/docs/Getting_Started.mdx @@ -73,6 +73,6 @@ ESLint will lint all TypeScript compatible files within the current folder, and ## Next Steps -We provide a plethora of powerful rules that utilize the power of TypeScript's type information. [Visit the next page for a setup guide](./linting/Typed_Linting.md 'Visit the next page for a typed rules setup guide'). +We provide a plethora of powerful rules that utilize the power of TypeScript's type information. [Visit the next page for a setup guide](./linting/Typed_Linting.mdx 'Visit the next page for a typed rules setup guide'). -If you're having problems getting this working, please have a look at our [Troubleshooting & FAQs](./linting/Troubleshooting.md). +If you're having problems getting this working, please have a look at our [Troubleshooting & FAQs](./linting/Troubleshooting.mdx). diff --git a/docs/MAINTENANCE.md b/docs/Maintenance.mdx similarity index 100% rename from docs/MAINTENANCE.md rename to docs/Maintenance.mdx diff --git a/docs/architecture/ESLint_Plugin.mdx b/docs/architecture/ESLint_Plugin.mdx index eca601415550..6db78ddfcebe 100644 --- a/docs/architecture/ESLint_Plugin.mdx +++ b/docs/architecture/ESLint_Plugin.mdx @@ -8,7 +8,7 @@ sidebar_label: eslint-plugin > The TypeScript plugin for ESLint. ✨ :::info -See [Getting Started](../Getting_Started.md) for documentation on how to lint your TypeScript code with ESLint. +See [Getting Started](../Getting_Started.mdx) for documentation on how to lint your TypeScript code with ESLint. ::: `@typescript-eslint/eslint-plugin` is an ESLint plugin used to load in custom rules and rule configurations lists from typescript-eslint. diff --git a/docs/architecture/ESLint_Plugin_TSLint.mdx b/docs/architecture/ESLint_Plugin_TSLint.mdx index 582d93aab8ef..525b1c072477 100644 --- a/docs/architecture/ESLint_Plugin_TSLint.mdx +++ b/docs/architecture/ESLint_Plugin_TSLint.mdx @@ -8,8 +8,8 @@ sidebar_label: eslint-plugin-tslint > ESLint plugin that allows running TSLint rules within ESLint to help you migrate from TSLint to ESLint. ✨ :::caution -Per [What About TSLint?](../linting/troubleshooting/TSLint.md), we highly recommend migrating off TSLint. -See [Getting Started](../Getting_Started.md) for documentation on how to lint your TypeScript code with ESLint. +Per [What About TSLint?](../linting/troubleshooting/TSLint.mdx), we highly recommend migrating off TSLint. +See [Getting Started](../Getting_Started.mdx) for documentation on how to lint your TypeScript code with ESLint. ::: ## Installation diff --git a/docs/architecture/Parser.mdx b/docs/architecture/Parser.mdx index d8d4f05ee43d..5039dc1bec55 100644 --- a/docs/architecture/Parser.mdx +++ b/docs/architecture/Parser.mdx @@ -30,6 +30,9 @@ The following additional configuration options are available by specifying them ```ts interface ParserOptions { + cacheLifetime?: { + glob?: number | 'Infinity'; + }; ecmaFeatures?: { jsx?: boolean; globalReturn?: boolean; @@ -49,6 +52,14 @@ interface ParserOptions { } ``` +### `cacheLifetime` + +This option allows you to granularly control our internal cache expiry lengths. + +You can specify the number of seconds as an integer number, or the string 'Infinity' if you never want the cache to expire. + +By default cache entries will be evicted after 30 seconds, or will persist indefinitely if the parser infers that it is a single run. + ### `ecmaFeatures` Optional additional options to describe how to parse the raw syntax. diff --git a/docs/architecture/TypeScript-ESTree.mdx b/docs/architecture/TypeScript-ESTree.mdx index 066030b0afee..f74aa65e2234 100644 --- a/docs/architecture/TypeScript-ESTree.mdx +++ b/docs/architecture/TypeScript-ESTree.mdx @@ -226,7 +226,23 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { allowAutomaticSingleRunInference?: boolean; /** - * Path to a file exporting a custom ModuleResolver. + * Granular control of the expiry lifetime of our internal caches. + * You can specify the number of seconds as an integer number, or the string + * 'Infinity' if you never want the cache to expire. + * + * By default cache entries will be evicted after 30 seconds, or will persist + * indefinitely if `allowAutomaticSingleRunInference = true` AND the parser + * infers that it is a single run. + */ + cacheLifetime?: { + /** + * Glob resolution for `parserOptions.project` values. + */ + glob?: number | 'Infinity'; + }; + + /** + * Path to a file exporting a custom `ModuleResolver`. */ moduleResolver?: string; } @@ -273,6 +289,23 @@ const { ast, services } = parseAndGenerateServices(code, { }); ``` +##### `ModuleResolver` + +The `moduleResolver` option allows you to specify the path to a module with a custom module resolver implementation. The module is expected to adhere to the following interface: + +```ts +interface ModuleResolver { + version: 1; + resolveModuleNames( + moduleNames: string[], + containingFile: string, + reusedNames: string[] | undefined, + redirectedReference: ts.ResolvedProjectReference | undefined, + options: ts.CompilerOptions, + ): (ts.ResolvedModule | undefined)[]; +} +``` + #### `parseWithNodeMaps(code, options)` Parses the given string of code with the options provided and returns both the ESTree-compatible AST as well as the node maps. diff --git a/docs/linting/CONFIGURATIONS.mdx b/docs/linting/CONFIGURATIONS.mdx index aec668a16dde..10d3f6293d54 100644 --- a/docs/linting/CONFIGURATIONS.mdx +++ b/docs/linting/CONFIGURATIONS.mdx @@ -18,7 +18,7 @@ Most projects should extend from at least one of: - [`strict`](#strict): Additional strict rules that can also catch bugs but are more opinionated than recommended rules. :::tip -We recommend most projects use [`recommended-requiring-type-checking`](#recommended-requiring-type-checking) (which requires [typed linting](./Typed_Linting.md)). +We recommend most projects use [`recommended-requiring-type-checking`](#recommended-requiring-type-checking) (which requires [typed linting](./Typed_Linting.mdx)). ::: :::note diff --git a/docs/linting/Troubleshooting.md b/docs/linting/Troubleshooting.mdx similarity index 99% rename from docs/linting/Troubleshooting.md rename to docs/linting/Troubleshooting.mdx index 67e4316608f2..cb59af284478 100644 --- a/docs/linting/Troubleshooting.md +++ b/docs/linting/Troubleshooting.mdx @@ -35,11 +35,11 @@ If you don't find an existing extension rule, or the extension rule doesn't work - If you **do not** want to lint the file: - Use [one of the options ESLint offers](https://eslint.org/docs/latest/user-guide/configuring/ignoring-code) to ignore files, namely a `.eslintignore` file, or `ignorePatterns` config. - If you **do** want to lint the file: - - If you **do not** want to lint the file with [type-aware linting](./Typed_Linting.md): + - If you **do not** want to lint the file with [type-aware linting](./Typed_Linting.mdx): - Use [ESLint's `overrides` configuration](https://eslint.org/docs/latest/user-guide/configuring/configuration-files#configuration-based-on-glob-patterns) to configure the file to not be parsed with type information. - A popular setup is to omit the above additions from top-level configuration and only apply them to TypeScript files via an override. - Alternatively, you can add `parserOptions: { project: null }` to an override for the files you wish to exclude. Note that `{ project: undefined }` will not work. - - If you **do** want to lint the file with [type-aware linting](./Typed_Linting.md): + - If you **do** want to lint the file with [type-aware linting](./Typed_Linting.mdx): - Check the `include` option of each of the tsconfigs that you provide to `parserOptions.project` - you must ensure that all files match an `include` glob, or else our tooling will not be able to find it. - If your file shouldn't be a part of one of your existing tsconfigs (for example, it is a script/tool local to the repo), then consider creating a new tsconfig (we advise calling it `tsconfig.eslint.json`) in your project root which lists this file in its `include`. For an example of this, you can check out the configuration we use in this repo: - [`tsconfig.eslint.json`](https://github.com/typescript-eslint/typescript-eslint/blob/main/tsconfig.eslint.json) @@ -64,7 +64,7 @@ For example, many projects have files like: In that case, viewing the `.eslintrc.cjs` in an IDE with the ESLint extension will show the error notice that the file couldn't be linted because it isn't included in `tsconfig.json`. -See our docs on [type aware linting](./Typed_Linting.md) for more information. +See our docs on [type aware linting](./Typed_Linting.mdx) for more information. ## I get errors telling me "The file must be included in at least one of the projects provided" diff --git a/docs/linting/Typed_Linting.md b/docs/linting/Typed_Linting.mdx similarity index 90% rename from docs/linting/Typed_Linting.md rename to docs/linting/Typed_Linting.mdx index 65e2c875fff9..7d9de2f25bb1 100644 --- a/docs/linting/Typed_Linting.md +++ b/docs/linting/Typed_Linting.mdx @@ -30,7 +30,7 @@ In more detail: - `plugin:@typescript-eslint/recommended-requiring-type-checking` is another [recommended configuration](./CONFIGURATIONS.mdx) we provide. This one contains recommended rules that additionally require type information. - `parserOptions.project` tells our parser the relative path where your project's `tsconfig.json` is. - - If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.md). + - If your project is a multi-package monorepo, see [our docs on configuring a monorepo](./typed-linting/Monorepos.mdx). - `parserOptions.tsconfigRootDir` tells our parser the absolute path of your project's root directory (see [Parser#tsconfigRootDir](../architecture/Parser.mdx#tsconfigRootDir)). With that done, run the same lint command you ran before. @@ -53,8 +53,8 @@ This means that generally they usually only run a complete lint before a push, o ### I get errors telling me "The file must be included in at least one of the projects provided" You're using an outdated version of `@typescript-eslint/parser`. -Update to the latest version to see a more informative version of this error message, explained in our [Troubleshooting and FAQs page](./Troubleshooting.md#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). +Update to the latest version to see a more informative version of this error message, explained in our [Troubleshooting and FAQs page](./Troubleshooting.mdx#i-get-errors-telling-me-eslint-was-configured-to-run--however-that-tsconfig-does-not--none-of-those-tsconfigs-include-this-file). ## Troubleshooting -If you're having problems getting this working, please have a look at our [Troubleshooting and FAQs page](./Troubleshooting.md). +If you're having problems getting this working, please have a look at our [Troubleshooting and FAQs page](./Troubleshooting.mdx). diff --git a/docs/linting/troubleshooting/Formatting.md b/docs/linting/troubleshooting/Formatting.mdx similarity index 100% rename from docs/linting/troubleshooting/Formatting.md rename to docs/linting/troubleshooting/Formatting.mdx diff --git a/docs/linting/troubleshooting/Performance.md b/docs/linting/troubleshooting/Performance.md index 6f696a7a05f0..f4d0f9e3d539 100644 --- a/docs/linting/troubleshooting/Performance.md +++ b/docs/linting/troubleshooting/Performance.md @@ -3,7 +3,7 @@ id: performance-troubleshooting title: Performance Troubleshooting --- -As mentioned in the [type-aware linting doc](../Typed_Linting.md), if you're using type-aware linting, your lint times should be roughly the same as your build times. +As mentioned in the [type-aware linting doc](../Typed_Linting.mdx), if you're using type-aware linting, your lint times should be roughly the same as your build times. If you're experiencing times much slower than that, then there are a few common culprits. @@ -53,7 +53,7 @@ Across a large codebase, these can add up, and severely impact performance. We recommend not using this rule, and instead using a tool like [`prettier`](https://www.npmjs.com/package/prettier) to enforce a standardized formatting. -See our [documentation on formatting](./Formatting.md) for more information. +See our [documentation on formatting](./Formatting.mdx) for more information. ## `eslint-plugin-prettier` diff --git a/docs/linting/troubleshooting/TSLint.md b/docs/linting/troubleshooting/TSLint.mdx similarity index 100% rename from docs/linting/troubleshooting/TSLint.md rename to docs/linting/troubleshooting/TSLint.mdx diff --git a/docs/linting/typed-linting/Monorepos.md b/docs/linting/typed-linting/Monorepos.mdx similarity index 96% rename from docs/linting/typed-linting/Monorepos.md rename to docs/linting/typed-linting/Monorepos.mdx index e55d20647796..7b01b81d6622 100644 --- a/docs/linting/typed-linting/Monorepos.md +++ b/docs/linting/typed-linting/Monorepos.mdx @@ -38,7 +38,7 @@ Be sure to update your `.eslintrc.js` to point at this new config file. ## One `tsconfig.json` per package (and an optional one in the root) -The `parserOptions.project` option introduced in [Linting with Type Information](../Typed_Linting.md) accepts an array of relative paths. +The `parserOptions.project` option introduced in [Linting with Type Information](../Typed_Linting.mdx) accepts an array of relative paths. Paths may be provided as [Node globs](https://github.com/isaacs/node-glob/blob/f5a57d3d6e19b324522a3fa5bdd5075fd1aa79d1/README.md#glob-primer). For each file being linted, the first matching project path will be used as its backing TSConfig. @@ -104,4 +104,4 @@ As an interim workaround, consider one of the following: ## Troubleshooting -If you're having problems getting this working, please have a look at our [Troubleshooting FAQ](../Troubleshooting.md). +If you're having problems getting this working, please have a look at our [Troubleshooting FAQ](../Troubleshooting.mdx). diff --git a/docs/maintenance/BRANDING.md b/docs/maintenance/Branding.mdx similarity index 92% rename from docs/maintenance/BRANDING.md rename to docs/maintenance/Branding.mdx index 25c08b216e65..d4e84d90deae 100644 --- a/docs/maintenance/BRANDING.md +++ b/docs/maintenance/Branding.mdx @@ -31,7 +31,12 @@ You can call it _blurple_ if you want. Our logo is also a halfway between [ESLint's logo](https://en.wikipedia.org/wiki/ESLint#/media/File:ESLint_logo.svg) and [TypeScript's logo](https://en.wikipedia.org/wiki/TypeScript#/media/File:Typescript.svg): -typescript-eslint logo +typescript-eslint logo - [Logo PNG download](/img/logo.png) - [Logo SVG download](/img/logo.svg) diff --git a/docs/maintenance/ISSUES.md b/docs/maintenance/Issues.mdx similarity index 99% rename from docs/maintenance/ISSUES.md rename to docs/maintenance/Issues.mdx index fb5eb655dd69..fc8a89711dee 100644 --- a/docs/maintenance/ISSUES.md +++ b/docs/maintenance/Issues.mdx @@ -105,7 +105,7 @@ We avoid features that: - Are only relevant for a minority of users, as they aren't likely worth the maintenance burden - Aren't TypeScript-specific (e.g. should be in ESLint core instead) - Are only relevant with specific userland frameworks or libraries, such as Jest or React -- Are for "formatting" functionality (we [strongly recommend users use a separate dedicated formatter](../linting/troubleshooting/Formatting.md)) +- Are for "formatting" functionality (we [strongly recommend users use a separate dedicated formatter](../linting/troubleshooting/Formatting.mdx)) #### ✨ Rule Enhancements diff --git a/docs/maintenance/PULL_REQUESTS.md b/docs/maintenance/Pull_Requests.mdx similarity index 100% rename from docs/maintenance/PULL_REQUESTS.md rename to docs/maintenance/Pull_Requests.mdx diff --git a/docs/maintenance/RELEASES.md b/docs/maintenance/Releases.mdx similarity index 100% rename from docs/maintenance/RELEASES.md rename to docs/maintenance/Releases.mdx diff --git a/docs/maintenance/Versioning.md b/docs/maintenance/Versioning.mdx similarity index 95% rename from docs/maintenance/Versioning.md rename to docs/maintenance/Versioning.mdx index d9a4224dbf8a..68a007c89c1a 100644 --- a/docs/maintenance/Versioning.md +++ b/docs/maintenance/Versioning.mdx @@ -14,11 +14,21 @@ Additionally, we promote to the `latest` tag on NPM once per week, **on Mondays The latest version under the `latest` tag is: -NPM Version + + NPM Version + The latest version under the `canary` tag **(latest commit to `main`)** is: -NPM Version + + NPM Version + :::note The only exception to the automated publishes described above is when we are in the final phases of creating the next major version of the libraries - e.g. going from `1.x.x` to `2.x.x`. diff --git a/docs/maintenance/issues/Rule_Deprecations.md b/docs/maintenance/issues/Rule_Deprecations.mdx similarity index 100% rename from docs/maintenance/issues/Rule_Deprecations.md rename to docs/maintenance/issues/Rule_Deprecations.mdx diff --git a/docs/maintenance/versioning/dependant-version-upgrades.mdx b/docs/maintenance/versioning/dependant-version-upgrades.mdx index 49ba51ce1e46..bf1a17f28296 100644 --- a/docs/maintenance/versioning/dependant-version-upgrades.mdx +++ b/docs/maintenance/versioning/dependant-version-upgrades.mdx @@ -21,7 +21,7 @@ Whenever you discover any new areas of work that are blocked by dropping an old 1. Upgrade the root `package.json` `devDependency` to the latest ESLint 1. Add the new major version to the explicit `peerDependency` versions 1. Check [`eslint-visitor-keys`](https://www.npmjs.com/package/eslint-visitor-keys) for a new version to be upgraded to as well. -1. Update [Versioning > ESLint](../Versioning.md#eslint) +1. Update [Versioning > ESLint](../Versioning.mdx#eslint) ### Removing Support for an Old ESLint Version @@ -32,7 +32,7 @@ Whenever you discover any new areas of work that are blocked by dropping an old - `/eslint.*5/i` - `/todo.*eslint.*5/i` - `/todo.*eslint/i` -1. Update [Versioning > ESLint](../Versioning.md#eslint) +1. Update [Versioning > ESLint](../Versioning.mdx#eslint) See [chore: drop support for ESLint v6](https://github.com/typescript-eslint/typescript-eslint/pull/5972) for reference. @@ -88,8 +88,8 @@ We generally start the process of supporting a new TypeScript version just after - In the root `package.json`, change the `devDependency` on `typescript` to `~X.Y.3` - Rename and update `patches/typescript*` to the new TypeScript version - Any other changes made necessary due to changes in TypeScript between the RC version and stable version - - Update the supported version range in [Versioning](../Versioning.md) -1. Update [Versioning > TypeScript](../Versioning.md#typescript) + - Update the supported version range in [Versioning](../Versioning.mdx) +1. Update [Versioning > TypeScript](../Versioning.mdx#typescript) 1. Send a PR that updates this documentation page to point to your newer issues and PRs - Also update any of these steps if you go with a different process @@ -108,7 +108,7 @@ A single PR can remove support for old TypeScript versions as a breaking change: 1. Update the root `package.json` `devDependency` 1. Update the `SUPPORTED_TYPESCRIPT_VERSIONS` constant in `warnAboutTSVersion.ts` 1. Update the `versions` constant in `version-check.ts` -1. Update [Versioning > TypeScript](../Versioning.md#typescript) +1. Update [Versioning > TypeScript](../Versioning.mdx#typescript) 1. Search for source code comments (excluding `CHANGELOG.md` files) that mention a now-unsupported version of TypeScript. - For example, to remove support for v4.3, searches might include: - `4.3` diff --git a/jest.config.base.js b/jest.config.base.js index 707c023f2695..48c306a70c06 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -18,7 +18,6 @@ module.exports = { 'json', 'node', ], - resolver: '/../../tests/jest-resolver.js', setupFilesAfterEnv: ['console-fail-test/setup.js'], testRegex: ['./tests/.+\\.test\\.ts$', './tests/.+\\.spec\\.ts$'], transform: { diff --git a/lerna.json b/lerna.json index e150bc530f7f..719e543d7471 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "5.50.0", + "version": "5.51.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index f8b24d34edf6..b8ee71a11c11 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,6 @@ "cross-fetch": "^3.1.5", "cspell": "^6.0.0", "downlevel-dts": ">=0.10.0", - "enhanced-resolve": "^5.9.3", "eslint": "^8.15.0", "eslint-plugin-deprecation": "^1.3.2", "eslint-plugin-eslint-comments": "^3.2.0", diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index 3b4168eff864..4b57fd921e85 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/ast-spec + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index c5e160a5c415..c1e472c43655 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "5.50.0", + "version": "5.51.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index 0cd6ad00030d..1cec28fd8aea 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-internal diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index 7b81526e7edd..f5d0e2b957e5 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "5.50.0", + "version": "5.51.0", "private": true, "main": "dist/index.js", "scripts": { @@ -14,9 +14,9 @@ }, "dependencies": { "@types/prettier": "*", - "@typescript-eslint/scope-manager": "5.50.0", - "@typescript-eslint/type-utils": "5.50.0", - "@typescript-eslint/utils": "5.50.0", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", "prettier": "*" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index b2551812889f..992cc4adb477 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 23bf62f3fc5c..764c382788dd 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "5.50.0", + "version": "5.51.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "ESLint plugin that wraps a TSLint configuration and lints the whole source using TSLint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/utils": "5.50.0", + "@typescript-eslint/utils": "5.51.0", "lodash": "^4.17.21" }, "peerDependencies": { @@ -48,6 +48,6 @@ }, "devDependencies": { "@types/lodash": "*", - "@typescript-eslint/parser": "5.50.0" + "@typescript-eslint/parser": "5.51.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 25cce92ccc4f..3df0f2a57df7 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,26 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + + +### Bug Fixes + +* **eslint-plugin:** [sort-type-constituents] fixed behavior change ([#6384](https://github.com/typescript-eslint/typescript-eslint/issues/6384)) ([5bf7f7f](https://github.com/typescript-eslint/typescript-eslint/commit/5bf7f7fe48aee61a676dfbe829c2a5e9e44cd552)), closes [#6339](https://github.com/typescript-eslint/typescript-eslint/issues/6339) +* **eslint-plugin:** do not use .at(), Node 14 does not support it ([#6402](https://github.com/typescript-eslint/typescript-eslint/issues/6402)) ([077ed1b](https://github.com/typescript-eslint/typescript-eslint/commit/077ed1b5be844df35b7fba554ddae579b3144787)) + + +### Features + +* **eslint-plugin:** [naming-convention] improve performance by removing unnecessary selectors ([#6376](https://github.com/typescript-eslint/typescript-eslint/issues/6376)) ([3647a1c](https://github.com/typescript-eslint/typescript-eslint/commit/3647a1c1bbcfe6551647632fc2d978fa90881de1)) +* **eslint-plugin:** [no-floating-promises] error on logical expression ([#6356](https://github.com/typescript-eslint/typescript-eslint/issues/6356)) ([f330e06](https://github.com/typescript-eslint/typescript-eslint/commit/f330e0651548d55163ddc3233c90fd3cbe37c9c0)) +* **eslint-plugin:** [no-import-type-side-effects] add rule to warn against runtime side effects with `verbatimModuleSyntax` ([#6394](https://github.com/typescript-eslint/typescript-eslint/issues/6394)) ([b14d3be](https://github.com/typescript-eslint/typescript-eslint/commit/b14d3be0f305d71e0adfc9381e9de993898b2b43)) +* **eslint-plugin:** [strict-boolean-expressions] add allow nullable enum to strict boolean expressions ([#6096](https://github.com/typescript-eslint/typescript-eslint/issues/6096)) ([d4747cd](https://github.com/typescript-eslint/typescript-eslint/commit/d4747cd8cc9dad2bf2cb64e1c0e8980ce34d82c7)) + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) diff --git a/packages/eslint-plugin/docs/rules/consistent-type-imports.md b/packages/eslint-plugin/docs/rules/consistent-type-imports.md index 045b7c2fa7da..baa41eccda18 100644 --- a/packages/eslint-plugin/docs/rules/consistent-type-imports.md +++ b/packages/eslint-plugin/docs/rules/consistent-type-imports.md @@ -95,3 +95,9 @@ If you are using [type-aware linting](https://typescript-eslint.io/linting/typed ## When Not To Use It - If you specifically want to use both import kinds for stylistic reasons, you can disable this rule. + +## Related To + +- [`no-import-type-side-effects`](./no-import-type-side-effects.md) +- [`import/consistent-type-specifier-style`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/consistent-type-specifier-style.md) +- [`import/no-duplicates` with `{"prefer-inline": true}`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md#inline-type-imports) diff --git a/packages/eslint-plugin/docs/rules/key-spacing.md b/packages/eslint-plugin/docs/rules/key-spacing.md index 3bfcf5f389f0..35108c28f862 100644 --- a/packages/eslint-plugin/docs/rules/key-spacing.md +++ b/packages/eslint-plugin/docs/rules/key-spacing.md @@ -8,5 +8,5 @@ description: 'Enforce consistent spacing between property names and type annotat ## Examples -This rule extends the base [`eslint/keyword-spacing`](https://eslint.org/docs/rules/key-spacing) rule. +This rule extends the base [`eslint/key-spacing`](https://eslint.org/docs/rules/key-spacing) rule. This version adds support for type annotations on interfaces, classes and type literals properties. diff --git a/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md b/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md new file mode 100644 index 000000000000..35b8f2c5282e --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-import-type-side-effects.md @@ -0,0 +1,75 @@ +--- +description: 'Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-import-type-side-effects** for documentation. + +The [`--verbatimModuleSyntax`](https://www.typescriptlang.org/tsconfig#verbatimModuleSyntax) compiler option causes TypeScript to do simple and predictable transpilation on import declarations. +Namely, it completely removes import declarations with a top-level `type` qualifier, and it removes any import specifiers with an inline `type` qualifier. + +The latter behavior does have one potentially surprising effect in that in certain cases TS can leave behind a "side effect" import at runtime: + +```ts +import { type A, type B } from 'mod'; + +// is transpiled to + +import {} from 'mod'; +// which is the same as +import 'mod'; +``` + +For the rare case of needing to import for side effects, this may be desirable - but for most cases you will not want to leave behind an unnecessary side effect import. + +## Examples + +This rule enforces that you use a top-level `type` qualifier for imports when it only imports specifiers with an inline `type` qualifier + + + +### ❌ Incorrect + +```ts +import { type A } from 'mod'; +import { type A as AA } from 'mod'; +import { type A, type B } from 'mod'; +import { type A as AA, type B as BB } from 'mod'; +``` + +### ✅ Correct + +```ts +import type { A } from 'mod'; +import type { A as AA } from 'mod'; +import type { A, B } from 'mod'; +import type { A as AA, B as BB } from 'mod'; + +import T from 'mod'; +import type T from 'mod'; + +import * as T from 'mod'; +import type * as T from 'mod'; + +import { T } from 'mod'; +import type { T } from 'mod'; +import { T, U } from 'mod'; +import type { T, U } from 'mod'; +import { type T, U } from 'mod'; +import { T, type U } from 'mod'; + +import type T, { U } from 'mod'; +import T, { type U } from 'mod'; +``` + +## When Not To Use It + +- If you want to leave behind side effect imports, then you shouldn't use this rule. +- If you're not using TypeScript 5.0's `verbatimModuleSyntax` option, then you don't need this rule. + +## Related To + +- [`consistent-type-imports`](./consistent-type-imports.md) +- [`import/consistent-type-specifier-style`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/consistent-type-specifier-style.md) +- [`import/no-duplicates` with `{"prefer-inline": true}`](https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-duplicates.md#inline-type-imports) diff --git a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md index b7e8c13a9be2..45a7f8f94962 100644 --- a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md +++ b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md @@ -124,6 +124,12 @@ Allows `number | null | undefined` in a boolean context. This is unsafe because nullable numbers can be either a falsy number or nullish. Set this to `true` if you don't mind implicitly treating zero or NaN the same as a nullish value. +### `allowNullableEnum` + +Allows `enum | null | undefined` in a boolean context. +This is unsafe because nullable enums can be either a falsy number or nullish. +Set this to `true` if you don't mind implicitly treating an enum whose value is zero the same as a nullish value. + ### `allowAny` Allows `any` in a boolean context. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 724b3eea0f1c..0a4fa810a7e4 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "5.50.0", + "version": "5.51.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -44,9 +44,9 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/scope-manager": "5.50.0", - "@typescript-eslint/type-utils": "5.50.0", - "@typescript-eslint/utils": "5.50.0", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/type-utils": "5.51.0", + "@typescript-eslint/utils": "5.51.0", "debug": "^4.3.4", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 452035c4ebf2..eb3856f10c3b 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -55,7 +55,6 @@ export = { 'no-dupe-class-members': 'off', '@typescript-eslint/no-dupe-class-members': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', - 'no-duplicate-imports': 'off', '@typescript-eslint/no-dynamic-delete': 'error', 'no-empty-function': 'off', '@typescript-eslint/no-empty-function': 'error', @@ -71,6 +70,7 @@ export = { '@typescript-eslint/no-for-in-array': 'error', 'no-implied-eval': 'off', '@typescript-eslint/no-implied-eval': 'error', + '@typescript-eslint/no-import-type-side-effects': 'error', '@typescript-eslint/no-inferrable-types': 'error', 'no-invalid-this': 'off', '@typescript-eslint/no-invalid-this': 'error', @@ -88,7 +88,6 @@ export = { '@typescript-eslint/no-non-null-asserted-nullish-coalescing': 'error', '@typescript-eslint/no-non-null-asserted-optional-chain': 'error', '@typescript-eslint/no-non-null-assertion': 'error', - '@typescript-eslint/parameter-properties': 'error', 'no-redeclare': 'off', '@typescript-eslint/no-redeclare': 'error', '@typescript-eslint/no-redundant-type-constituents': 'error', @@ -128,6 +127,7 @@ export = { '@typescript-eslint/object-curly-spacing': 'error', 'padding-line-between-statements': 'off', '@typescript-eslint/padding-line-between-statements': 'error', + '@typescript-eslint/parameter-properties': 'error', '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-enum-initializers': 'error', '@typescript-eslint/prefer-for-of': 'error', diff --git a/packages/eslint-plugin/src/configs/strict.ts b/packages/eslint-plugin/src/configs/strict.ts index ccd44b85a0a1..99b4e83b5081 100644 --- a/packages/eslint-plugin/src/configs/strict.ts +++ b/packages/eslint-plugin/src/configs/strict.ts @@ -8,8 +8,8 @@ export = { '@typescript-eslint/array-type': 'warn', '@typescript-eslint/ban-tslint-comment': 'warn', '@typescript-eslint/class-literal-property-style': 'warn', - '@typescript-eslint/consistent-indexed-object-style': 'warn', '@typescript-eslint/consistent-generic-constructors': 'warn', + '@typescript-eslint/consistent-indexed-object-style': 'warn', '@typescript-eslint/consistent-type-assertions': 'warn', '@typescript-eslint/consistent-type-definitions': 'warn', 'dot-notation': 'off', diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts index 5f5ddfc0aad6..498a9bf5ae1b 100644 --- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts +++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts @@ -65,7 +65,7 @@ export default util.createRule({ case AST_NODE_TYPES.TSDeclareFunction: case AST_NODE_TYPES.FunctionDeclaration: { const name = member.id?.name ?? null; - if (name === null) { + if (name == null) { return null; } return { @@ -143,7 +143,7 @@ export default util.createRule({ members.forEach(member => { const method = getMemberMethod(member); - if (method === null) { + if (method == null) { lastMethod = null; return; } diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index dce411405155..f21dda8a249a 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -36,7 +36,7 @@ function stringifyNode( function getCustomMessage( bannedType: null | string | { message?: string; fixWith?: string }, ): string { - if (bannedType === null) { + if (bannedType == null) { return ''; } diff --git a/packages/eslint-plugin/src/rules/comma-spacing.ts b/packages/eslint-plugin/src/rules/comma-spacing.ts index fda50d1b2e4a..a1ebcc181f28 100644 --- a/packages/eslint-plugin/src/rules/comma-spacing.ts +++ b/packages/eslint-plugin/src/rules/comma-spacing.ts @@ -68,7 +68,7 @@ export default createRule({ let previousToken = sourceCode.getFirstToken(node); for (const element of node.elements) { let token: TSESTree.Token | null; - if (element === null) { + if (element == null) { token = sourceCode.getTokenAfter(previousToken!); if (token && isCommaToken(token)) { ignoredTokens.add(token); diff --git a/packages/eslint-plugin/src/rules/consistent-type-imports.ts b/packages/eslint-plugin/src/rules/consistent-type-imports.ts index a812116f2f8b..4c5cf771901d 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-imports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-imports.ts @@ -1,5 +1,5 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as util from '../util'; @@ -32,18 +32,6 @@ interface ReportValueImport { inlineTypeSpecifiers: TSESTree.ImportSpecifier[]; } -function isImportToken( - token: TSESTree.Token, -): token is TSESTree.KeywordToken & { value: 'import' } { - return token.type === AST_TOKEN_TYPES.Keyword && token.value === 'import'; -} - -function isTypeToken( - token: TSESTree.Token, -): token is TSESTree.IdentifierToken & { value: 'type' } { - return token.type === AST_TOKEN_TYPES.Identifier && token.value === 'type'; -} - type MessageIds = | 'typeOverValue' | 'someImportsAreOnlyTypes' @@ -751,7 +739,7 @@ export default util.createRule({ ) { if (report.typeSpecifiers.length === node.specifiers.length) { const importToken = util.nullThrows( - sourceCode.getFirstToken(node, isImportToken), + sourceCode.getFirstToken(node, util.isImportKeyword), util.NullThrowsReasons.MissingToken('import', node.type), ); // import type Type from 'foo' @@ -800,7 +788,7 @@ export default util.createRule({ // import type Foo from 'foo' // ^^^^^ insert const importToken = util.nullThrows( - sourceCode.getFirstToken(node, isImportToken), + sourceCode.getFirstToken(node, util.isImportKeyword), util.NullThrowsReasons.MissingToken('import', node.type), ); yield fixer.insertTextAfter(importToken, ' type'); @@ -945,14 +933,14 @@ export default util.createRule({ // import type Foo from 'foo' // ^^^^ remove const importToken = util.nullThrows( - sourceCode.getFirstToken(node, isImportToken), + sourceCode.getFirstToken(node, util.isImportKeyword), util.NullThrowsReasons.MissingToken('import', node.type), ); const typeToken = util.nullThrows( sourceCode.getFirstTokenBetween( importToken, node.specifiers[0]?.local ?? node.source, - isTypeToken, + util.isTypeKeyword, ), util.NullThrowsReasons.MissingToken('type', node.type), ); @@ -970,7 +958,7 @@ export default util.createRule({ // import { type Foo } from 'foo' // ^^^^ remove const typeToken = util.nullThrows( - sourceCode.getFirstToken(node, isTypeToken), + sourceCode.getFirstToken(node, util.isTypeKeyword), util.NullThrowsReasons.MissingToken('type', node.type), ); const afterToken = util.nullThrows( diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index f7e51fdabd58..bbddfc8d4709 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -48,6 +48,7 @@ import noFloatingPromises from './no-floating-promises'; import noForInArray from './no-for-in-array'; import noImplicitAnyCatch from './no-implicit-any-catch'; import noImpliedEval from './no-implied-eval'; +import noImportTypeSideEffects from './no-import-type-side-effects'; import noInferrableTypes from './no-inferrable-types'; import noInvalidThis from './no-invalid-this'; import noInvalidVoidType from './no-invalid-void-type'; @@ -180,6 +181,7 @@ export default { 'no-for-in-array': noForInArray, 'no-implicit-any-catch': noImplicitAnyCatch, 'no-implied-eval': noImpliedEval, + 'no-import-type-side-effects': noImportTypeSideEffects, 'no-inferrable-types': noInferrableTypes, 'no-invalid-this': noInvalidThis, 'no-invalid-void-type': noInvalidVoidType, diff --git a/packages/eslint-plugin/src/rules/key-spacing.ts b/packages/eslint-plugin/src/rules/key-spacing.ts index 587d2674f4f3..2562107ee050 100644 --- a/packages/eslint-plugin/src/rules/key-spacing.ts +++ b/packages/eslint-plugin/src/rules/key-spacing.ts @@ -14,6 +14,16 @@ const baseSchema = Array.isArray(baseRule.meta.schema) ? baseRule.meta.schema[0] : baseRule.meta.schema; +/** + * TODO: replace with native .at() once Node 14 stops being supported + */ +function at(arr: T[], position: number): T | undefined { + if (position < 0) { + return arr[arr.length + position]; + } + return arr[position]; +} + export default util.createRule({ name: 'key-spacing', meta: { @@ -41,7 +51,7 @@ export default util.createRule({ function adjustedColumn(position: TSESTree.Position): number { const line = position.line - 1; // position.line is 1-indexed return util.getStringLength( - sourceCode.lines[line].slice(0, position.column), + at(sourceCode.lines, line)!.slice(0, position.column), ); } @@ -87,7 +97,7 @@ export default util.createRule({ return code.slice( 0, sourceCode.getTokenAfter( - node.parameters.at(-1)!, + at(node.parameters, -1)!, util.isClosingBracketToken, )!.range[1] - node.range[0], ); @@ -102,7 +112,7 @@ export default util.createRule({ return getLastTokenBeforeColon( node.type !== AST_NODE_TYPES.TSIndexSignature ? node.key - : node.parameters.at(-1)!, + : at(node.parameters, -1)!, ).loc.end; } @@ -202,7 +212,7 @@ export default util.createRule({ if ( leadingComments.length && leadingComments[0].loc.start.line - groupEndLine <= 1 && - candidateValueStartLine - leadingComments.at(-1)!.loc.end.line <= 1 + candidateValueStartLine - at(leadingComments, -1)!.loc.end.line <= 1 ) { for (let i = 1; i < leadingComments.length; i++) { if ( @@ -373,7 +383,7 @@ export default util.createRule({ let prevNode: TSESTree.Node | undefined = undefined; for (const node of members) { - let prevAlignedNode = currentAlignGroup.at(-1); + let prevAlignedNode = at(currentAlignGroup, -1); if (prevAlignedNode !== prevNode) { prevAlignedNode = undefined; } diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 81e0ae6484e9..f8db5ade0caf 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -489,7 +489,7 @@ function getRank( ): number { const type = getNodeType(node); - if (type === null) { + if (type == null) { // shouldn't happen but just in case, put it on the end return orderConfig.length - 1; } @@ -842,7 +842,7 @@ export default util.createRule({ supportsModifiers, ); - if (grouped === null) { + if (grouped == null) { return false; } diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/types.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/types.ts index e900b4c5f171..d5c15994b8bb 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/types.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/types.ts @@ -64,7 +64,7 @@ type ValidatorFunction = ( node: TSESTree.Identifier | TSESTree.PrivateIdentifier | TSESTree.Literal, modifiers?: Set, ) => void; -type ParsedOptions = Record; +type ParsedOptions = Record; type Context = Readonly>; export type { diff --git a/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts b/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts index e96ff19e3748..c2b87ccc33b1 100644 --- a/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts +++ b/packages/eslint-plugin/src/rules/naming-convention-utils/validator.ts @@ -101,25 +101,25 @@ function createValidator( let name: string | null = originalName; name = validateUnderscore('leading', config, name, node, originalName); - if (name === null) { + if (name == null) { // fail return; } name = validateUnderscore('trailing', config, name, node, originalName); - if (name === null) { + if (name == null) { // fail return; } name = validateAffix('prefix', config, name, node, originalName); - if (name === null) { + if (name == null) { // fail return; } name = validateAffix('suffix', config, name, node, originalName); - if (name === null) { + if (name == null) { // fail return; } @@ -383,7 +383,7 @@ function createValidator( modifiers: Set, ): boolean { const formats = config.format; - if (formats === null || formats.length === 0) { + if (!formats?.length) { return true; } @@ -427,7 +427,7 @@ function isCorrectType( context: Context, selector: Selectors, ): boolean { - if (config.types === null) { + if (config.types == null) { return true; } diff --git a/packages/eslint-plugin/src/rules/naming-convention.ts b/packages/eslint-plugin/src/rules/naming-convention.ts index 3b500b0b347e..f5a59614851d 100644 --- a/packages/eslint-plugin/src/rules/naming-convention.ts +++ b/packages/eslint-plugin/src/rules/naming-convention.ts @@ -95,7 +95,7 @@ export default util.createRule({ .getParserServices(context, true) .program.getCompilerOptions(); function handleMember( - validator: ValidatorFunction | null, + validator: ValidatorFunction, node: | TSESTree.PropertyNonComputedName | TSESTree.PropertyDefinitionNonComputedName @@ -215,176 +215,198 @@ export default util.createRule({ ); } - return { + const selectors: { + readonly [k in keyof TSESLint.RuleListener]: Readonly<{ + validator: ValidatorFunction; + handler: ( + node: Parameters>[0], + validator: ValidatorFunction, + ) => void; + }>; + } = { // #region variable - VariableDeclarator(node: TSESTree.VariableDeclarator): void { - const validator = validators.variable; - if (!validator) { - return; - } - const identifiers = getIdentifiersFromPattern(node.id); + VariableDeclarator: { + validator: validators.variable, + handler: (node, validator): void => { + const identifiers = getIdentifiersFromPattern(node.id); - const baseModifiers = new Set(); - const parent = node.parent; - if (parent?.type === AST_NODE_TYPES.VariableDeclaration) { - if (parent.kind === 'const') { - baseModifiers.add(Modifiers.const); - } + const baseModifiers = new Set(); + const parent = node.parent; + if (parent?.type === AST_NODE_TYPES.VariableDeclaration) { + if (parent.kind === 'const') { + baseModifiers.add(Modifiers.const); + } - if (isGlobal(context.getScope())) { - baseModifiers.add(Modifiers.global); + if (isGlobal(context.getScope())) { + baseModifiers.add(Modifiers.global); + } } - } - identifiers.forEach(id => { - const modifiers = new Set(baseModifiers); + identifiers.forEach(id => { + const modifiers = new Set(baseModifiers); - if (isDestructured(id)) { - modifiers.add(Modifiers.destructured); - } + if (isDestructured(id)) { + modifiers.add(Modifiers.destructured); + } - if (isExported(parent, id.name, context.getScope())) { - modifiers.add(Modifiers.exported); - } + if (isExported(parent, id.name, context.getScope())) { + modifiers.add(Modifiers.exported); + } - if (isUnused(id.name)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(id.name)) { + modifiers.add(Modifiers.unused); + } - if (isAsyncVariableIdentifier(id)) { - modifiers.add(Modifiers.async); - } + if (isAsyncVariableIdentifier(id)) { + modifiers.add(Modifiers.async); + } - validator(id, modifiers); - }); + validator(id, modifiers); + }); + }, }, // #endregion // #region function - 'FunctionDeclaration, TSDeclareFunction, FunctionExpression'( - node: - | TSESTree.FunctionDeclaration - | TSESTree.TSDeclareFunction - | TSESTree.FunctionExpression, - ): void { - const validator = validators.function; - if (!validator || node.id === null) { - return; - } + 'FunctionDeclaration, TSDeclareFunction, FunctionExpression': { + validator: validators.function, + handler: ( + node: + | TSESTree.FunctionDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.FunctionExpression, + validator, + ): void => { + if (node.id == null) { + return; + } - const modifiers = new Set(); - // functions create their own nested scope - const scope = context.getScope().upper; + const modifiers = new Set(); + // functions create their own nested scope + const scope = context.getScope().upper; - if (isGlobal(scope)) { - modifiers.add(Modifiers.global); - } + if (isGlobal(scope)) { + modifiers.add(Modifiers.global); + } - if (isExported(node, node.id.name, scope)) { - modifiers.add(Modifiers.exported); - } + if (isExported(node, node.id.name, scope)) { + modifiers.add(Modifiers.exported); + } - if (isUnused(node.id.name, scope)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(node.id.name, scope)) { + modifiers.add(Modifiers.unused); + } - if (node.async) { - modifiers.add(Modifiers.async); - } + if (node.async) { + modifiers.add(Modifiers.async); + } - validator(node.id, modifiers); + validator(node.id, modifiers); + }, }, // #endregion function // #region parameter - 'FunctionDeclaration, TSDeclareFunction, TSEmptyBodyFunctionExpression, FunctionExpression, ArrowFunctionExpression'( - node: - | TSESTree.FunctionDeclaration - | TSESTree.TSDeclareFunction - | TSESTree.TSEmptyBodyFunctionExpression - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression, - ): void { - const validator = validators.parameter; - if (!validator) { - return; - } - - node.params.forEach(param => { - if (param.type === AST_NODE_TYPES.TSParameterProperty) { - return; - } - - const identifiers = getIdentifiersFromPattern(param); - - identifiers.forEach(i => { - const modifiers = new Set(); - - if (isDestructured(i)) { - modifiers.add(Modifiers.destructured); - } - - if (isUnused(i.name)) { - modifiers.add(Modifiers.unused); - } - - validator(i, modifiers); - }); - }); - }, + 'FunctionDeclaration, TSDeclareFunction, TSEmptyBodyFunctionExpression, FunctionExpression, ArrowFunctionExpression': + { + validator: validators.parameter, + handler: ( + node: + | TSESTree.FunctionDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.TSEmptyBodyFunctionExpression + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, + validator, + ): void => { + node.params.forEach(param => { + if (param.type === AST_NODE_TYPES.TSParameterProperty) { + return; + } + + const identifiers = getIdentifiersFromPattern(param); + + identifiers.forEach(i => { + const modifiers = new Set(); + + if (isDestructured(i)) { + modifiers.add(Modifiers.destructured); + } + + if (isUnused(i.name)) { + modifiers.add(Modifiers.unused); + } + + validator(i, modifiers); + }); + }); + }, + }, // #endregion parameter // #region parameterProperty - TSParameterProperty(node): void { - const validator = validators.parameterProperty; - if (!validator) { - return; - } - - const modifiers = getMemberModifiers(node); + TSParameterProperty: { + validator: validators.parameterProperty, + handler: (node, validator): void => { + const modifiers = getMemberModifiers(node); - const identifiers = getIdentifiersFromPattern(node.parameter); + const identifiers = getIdentifiersFromPattern(node.parameter); - identifiers.forEach(i => { - validator(i, modifiers); - }); + identifiers.forEach(i => { + validator(i, modifiers); + }); + }, }, // #endregion parameterProperty // #region property - ':not(ObjectPattern) > Property[computed = false][kind = "init"][value.type != "ArrowFunctionExpression"][value.type != "FunctionExpression"][value.type != "TSEmptyBodyFunctionExpression"]'( - node: TSESTree.PropertyNonComputedName, - ): void { - const modifiers = new Set([Modifiers.public]); - handleMember(validators.objectLiteralProperty, node, modifiers); - }, - - ':matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type != "ArrowFunctionExpression"][value.type != "FunctionExpression"][value.type != "TSEmptyBodyFunctionExpression"]'( - node: - | TSESTree.PropertyDefinitionNonComputedName - | TSESTree.TSAbstractPropertyDefinitionNonComputedName, - ): void { - const modifiers = getMemberModifiers(node); - handleMember(validators.classProperty, node, modifiers); - }, - - 'TSPropertySignature[computed = false]'( - node: TSESTree.TSPropertySignatureNonComputedName, - ): void { - const modifiers = new Set([Modifiers.public]); - if (node.readonly) { - modifiers.add(Modifiers.readonly); - } + ':not(ObjectPattern) > Property[computed = false][kind = "init"][value.type != "ArrowFunctionExpression"][value.type != "FunctionExpression"][value.type != "TSEmptyBodyFunctionExpression"]': + { + validator: validators.objectLiteralProperty, + handler: ( + node: TSESTree.PropertyNonComputedName, + validator, + ): void => { + const modifiers = new Set([Modifiers.public]); + handleMember(validator, node, modifiers); + }, + }, + + ':matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type != "ArrowFunctionExpression"][value.type != "FunctionExpression"][value.type != "TSEmptyBodyFunctionExpression"]': + { + validator: validators.classProperty, + handler: ( + node: + | TSESTree.PropertyDefinitionNonComputedName + | TSESTree.TSAbstractPropertyDefinitionNonComputedName, + validator, + ): void => { + const modifiers = getMemberModifiers(node); + handleMember(validator, node, modifiers); + }, + }, + + 'TSPropertySignature[computed = false]': { + validator: validators.typeProperty, + handler: ( + node: TSESTree.TSPropertySignatureNonComputedName, + validator, + ): void => { + const modifiers = new Set([Modifiers.public]); + if (node.readonly) { + modifiers.add(Modifiers.readonly); + } - handleMember(validators.typeProperty, node, modifiers); + handleMember(validator, node, modifiers); + }, }, // #endregion property @@ -395,18 +417,22 @@ export default util.createRule({ 'Property[computed = false][kind = "init"][value.type = "ArrowFunctionExpression"]', 'Property[computed = false][kind = "init"][value.type = "FunctionExpression"]', 'Property[computed = false][kind = "init"][value.type = "TSEmptyBodyFunctionExpression"]', - ].join(', ')]( - node: - | TSESTree.PropertyNonComputedName - | TSESTree.TSMethodSignatureNonComputedName, - ): void { - const modifiers = new Set([Modifiers.public]); - - if (isAsyncMemberOrProperty(node)) { - modifiers.add(Modifiers.async); - } + ].join(', ')]: { + validator: validators.objectLiteralMethod, + handler: ( + node: + | TSESTree.PropertyNonComputedName + | TSESTree.TSMethodSignatureNonComputedName, + validator, + ): void => { + const modifiers = new Set([Modifiers.public]); + + if (isAsyncMemberOrProperty(node)) { + modifiers.add(Modifiers.async); + } - handleMember(validators.objectLiteralMethod, node, modifiers); + handleMember(validator, node, modifiers); + }, }, [[ @@ -414,203 +440,218 @@ export default util.createRule({ ':matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type = "FunctionExpression"]', ':matches(PropertyDefinition, TSAbstractPropertyDefinition)[computed = false][value.type = "TSEmptyBodyFunctionExpression"]', ':matches(MethodDefinition, TSAbstractMethodDefinition)[computed = false][kind = "method"]', - ].join(', ')]( - node: - | TSESTree.PropertyDefinitionNonComputedName - | TSESTree.TSAbstractPropertyDefinitionNonComputedName - | TSESTree.MethodDefinitionNonComputedName - | TSESTree.TSAbstractMethodDefinitionNonComputedName, - ): void { - const modifiers = getMemberModifiers(node); - - if (isAsyncMemberOrProperty(node)) { - modifiers.add(Modifiers.async); - } + ].join(', ')]: { + validator: validators.classMethod, + handler: ( + node: + | TSESTree.PropertyDefinitionNonComputedName + | TSESTree.TSAbstractPropertyDefinitionNonComputedName + | TSESTree.MethodDefinitionNonComputedName + | TSESTree.TSAbstractMethodDefinitionNonComputedName, + validator, + ): void => { + const modifiers = getMemberModifiers(node); + + if (isAsyncMemberOrProperty(node)) { + modifiers.add(Modifiers.async); + } - handleMember(validators.classMethod, node, modifiers); + handleMember(validator, node, modifiers); + }, }, - 'TSMethodSignature[computed = false]'( - node: TSESTree.TSMethodSignatureNonComputedName, - ): void { - const modifiers = new Set([Modifiers.public]); - handleMember(validators.typeMethod, node, modifiers); + 'TSMethodSignature[computed = false]': { + validator: validators.typeMethod, + handler: ( + node: TSESTree.TSMethodSignatureNonComputedName, + validator, + ): void => { + const modifiers = new Set([Modifiers.public]); + handleMember(validator, node, modifiers); + }, }, // #endregion method // #region accessor - 'Property[computed = false]:matches([kind = "get"], [kind = "set"])'( - node: TSESTree.PropertyNonComputedName, - ): void { - const modifiers = new Set([Modifiers.public]); - handleMember(validators.accessor, node, modifiers); + 'Property[computed = false]:matches([kind = "get"], [kind = "set"])': { + validator: validators.accessor, + handler: (node: TSESTree.PropertyNonComputedName, validator): void => { + const modifiers = new Set([Modifiers.public]); + handleMember(validator, node, modifiers); + }, }, - 'MethodDefinition[computed = false]:matches([kind = "get"], [kind = "set"])'( - node: TSESTree.MethodDefinitionNonComputedName, - ): void { - const modifiers = getMemberModifiers(node); - handleMember(validators.accessor, node, modifiers); - }, + 'MethodDefinition[computed = false]:matches([kind = "get"], [kind = "set"])': + { + validator: validators.accessor, + handler: ( + node: TSESTree.MethodDefinitionNonComputedName, + validator, + ): void => { + const modifiers = getMemberModifiers(node); + handleMember(validator, node, modifiers); + }, + }, // #endregion accessor // #region enumMember // computed is optional, so can't do [computed = false] - 'TSEnumMember[computed != true]'( - node: TSESTree.TSEnumMemberNonComputedName, - ): void { - const validator = validators.enumMember; - if (!validator) { - return; - } - - const id = node.id; - const modifiers = new Set(); - - if (requiresQuoting(id, compilerOptions.target)) { - modifiers.add(Modifiers.requiresQuotes); - } + 'TSEnumMember[computed != true]': { + validator: validators.enumMember, + handler: ( + node: TSESTree.TSEnumMemberNonComputedName, + validator, + ): void => { + const id = node.id; + const modifiers = new Set(); + + if (requiresQuoting(id, compilerOptions.target)) { + modifiers.add(Modifiers.requiresQuotes); + } - validator(id, modifiers); + validator(id, modifiers); + }, }, // #endregion enumMember // #region class - 'ClassDeclaration, ClassExpression'( - node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, - ): void { - const validator = validators.class; - if (!validator) { - return; - } - - const id = node.id; - if (id === null) { - return; - } + 'ClassDeclaration, ClassExpression': { + validator: validators.class, + handler: ( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, + validator, + ): void => { + const id = node.id; + if (id == null) { + return; + } - const modifiers = new Set(); - // classes create their own nested scope - const scope = context.getScope().upper; + const modifiers = new Set(); + // classes create their own nested scope + const scope = context.getScope().upper; - if (node.abstract) { - modifiers.add(Modifiers.abstract); - } + if (node.abstract) { + modifiers.add(Modifiers.abstract); + } - if (isExported(node, id.name, scope)) { - modifiers.add(Modifiers.exported); - } + if (isExported(node, id.name, scope)) { + modifiers.add(Modifiers.exported); + } - if (isUnused(id.name, scope)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(id.name, scope)) { + modifiers.add(Modifiers.unused); + } - validator(id, modifiers); + validator(id, modifiers); + }, }, // #endregion class // #region interface - TSInterfaceDeclaration(node): void { - const validator = validators.interface; - if (!validator) { - return; - } - - const modifiers = new Set(); - const scope = context.getScope(); + TSInterfaceDeclaration: { + validator: validators.interface, + handler: (node, validator): void => { + const modifiers = new Set(); + const scope = context.getScope(); - if (isExported(node, node.id.name, scope)) { - modifiers.add(Modifiers.exported); - } + if (isExported(node, node.id.name, scope)) { + modifiers.add(Modifiers.exported); + } - if (isUnused(node.id.name, scope)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(node.id.name, scope)) { + modifiers.add(Modifiers.unused); + } - validator(node.id, modifiers); + validator(node.id, modifiers); + }, }, // #endregion interface // #region typeAlias - TSTypeAliasDeclaration(node): void { - const validator = validators.typeAlias; - if (!validator) { - return; - } + TSTypeAliasDeclaration: { + validator: validators.typeAlias, + handler: (node, validator): void => { + const modifiers = new Set(); + const scope = context.getScope(); - const modifiers = new Set(); - const scope = context.getScope(); - - if (isExported(node, node.id.name, scope)) { - modifiers.add(Modifiers.exported); - } + if (isExported(node, node.id.name, scope)) { + modifiers.add(Modifiers.exported); + } - if (isUnused(node.id.name, scope)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(node.id.name, scope)) { + modifiers.add(Modifiers.unused); + } - validator(node.id, modifiers); + validator(node.id, modifiers); + }, }, // #endregion typeAlias // #region enum - TSEnumDeclaration(node): void { - const validator = validators.enum; - if (!validator) { - return; - } - - const modifiers = new Set(); - // enums create their own nested scope - const scope = context.getScope().upper; + TSEnumDeclaration: { + validator: validators.enum, + handler: (node, validator): void => { + const modifiers = new Set(); + // enums create their own nested scope + const scope = context.getScope().upper; - if (isExported(node, node.id.name, scope)) { - modifiers.add(Modifiers.exported); - } + if (isExported(node, node.id.name, scope)) { + modifiers.add(Modifiers.exported); + } - if (isUnused(node.id.name, scope)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(node.id.name, scope)) { + modifiers.add(Modifiers.unused); + } - validator(node.id, modifiers); + validator(node.id, modifiers); + }, }, // #endregion enum // #region typeParameter - 'TSTypeParameterDeclaration > TSTypeParameter'( - node: TSESTree.TSTypeParameter, - ): void { - const validator = validators.typeParameter; - if (!validator) { - return; - } - - const modifiers = new Set(); - const scope = context.getScope(); + 'TSTypeParameterDeclaration > TSTypeParameter': { + validator: validators.typeParameter, + handler: (node: TSESTree.TSTypeParameter, validator): void => { + const modifiers = new Set(); + const scope = context.getScope(); - if (isUnused(node.name.name, scope)) { - modifiers.add(Modifiers.unused); - } + if (isUnused(node.name.name, scope)) { + modifiers.add(Modifiers.unused); + } - validator(node.name, modifiers); + validator(node.name, modifiers); + }, }, // #endregion typeParameter }; + + return Object.fromEntries( + Object.entries(selectors) + .map(([selector, { validator, handler }]) => { + return [ + selector, + (node: Parameters[0]): void => { + handler(node, validator); + }, + ] as const; + }) + .filter((s): s is NonNullable => s != null), + ); }, }); diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 61829743e81b..c4ce3db8e1cc 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -4,6 +4,7 @@ import * as tsutils from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; +import { OperatorPrecedence } from '../util'; type Options = [ { @@ -66,7 +67,6 @@ export default util.createRule({ create(context, [options]) { const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); - const sourceCode = context.getSourceCode(); return { ExpressionStatement(node): void { @@ -88,10 +88,21 @@ export default util.createRule({ suggest: [ { messageId: 'floatingFixVoid', - fix(fixer): TSESLint.RuleFix { - let code = sourceCode.getText(node); - code = `void ${code}`; - return fixer.replaceText(node, code); + fix(fixer): TSESLint.RuleFix | TSESLint.RuleFix[] { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get( + node.expression, + ); + if (isHigherPrecedenceThanUnary(tsNode)) { + return fixer.insertTextBefore(node, 'void '); + } else { + return [ + fixer.insertTextBefore(node, 'void ('), + fixer.insertTextAfterRange( + [expression.range[1], expression.range[1]], + ')', + ), + ]; + } }, }, ], @@ -116,7 +127,7 @@ export default util.createRule({ const tsNode = parserServices.esTreeNodeToTSNodeMap.get( node.expression, ); - if (isHigherPrecedenceThanAwait(tsNode)) { + if (isHigherPrecedenceThanUnary(tsNode)) { return fixer.insertTextBefore(node, 'await '); } else { return [ @@ -136,16 +147,12 @@ export default util.createRule({ }, }; - function isHigherPrecedenceThanAwait(node: ts.Node): boolean { + function isHigherPrecedenceThanUnary(node: ts.Node): boolean { const operator = tsutils.isBinaryExpression(node) ? node.operatorToken.kind : ts.SyntaxKind.Unknown; const nodePrecedence = util.getOperatorPrecedence(node.kind, operator); - const awaitPrecedence = util.getOperatorPrecedence( - ts.SyntaxKind.AwaitExpression, - ts.SyntaxKind.Unknown, - ); - return nodePrecedence > awaitPrecedence; + return nodePrecedence > OperatorPrecedence.Unary; } function isAsyncIife(node: TSESTree.ExpressionStatement): boolean { @@ -214,6 +221,11 @@ export default util.createRule({ // `new Promise()`), the promise is not handled because it doesn't have the // necessary then/catch call at the end of the chain. return true; + } else if (node.type === AST_NODE_TYPES.LogicalExpression) { + return ( + isUnhandledPromise(checker, node.left) || + isUnhandledPromise(checker, node.right) + ); } // We conservatively return false for all other types of expressions because diff --git a/packages/eslint-plugin/src/rules/no-implied-eval.ts b/packages/eslint-plugin/src/rules/no-implied-eval.ts index 0ae6698c533c..d88cd05ff6f4 100644 --- a/packages/eslint-plugin/src/rules/no-implied-eval.ts +++ b/packages/eslint-plugin/src/rules/no-implied-eval.ts @@ -135,7 +135,7 @@ export default util.createRule({ node: TSESTree.NewExpression | TSESTree.CallExpression, ): void { const calleeName = getCalleeName(node.callee); - if (calleeName === null) { + if (calleeName == null) { return; } diff --git a/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts b/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts new file mode 100644 index 000000000000..ce80a654afe5 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-import-type-side-effects.ts @@ -0,0 +1,76 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import * as util from '../util'; + +type Options = []; +type MessageIds = 'useTopLevelQualifier'; + +export default util.createRule({ + name: 'no-import-type-side-effects', + meta: { + type: 'problem', + docs: { + description: + 'Enforce the use of top-level import type qualifier when an import only has specifiers with inline type qualifiers', + recommended: false, + }, + fixable: 'code', + messages: { + useTopLevelQualifier: + 'TypeScript will only remove the inline type specifiers which will leave behind a side effect import at runtime. Convert this to a top-level type qualifier to properly remove the entire import.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const sourceCode = context.getSourceCode(); + return { + 'ImportDeclaration[importKind!="type"]'( + node: TSESTree.ImportDeclaration, + ): void { + const specifiers: TSESTree.ImportSpecifier[] = []; + for (const specifier of node.specifiers) { + if ( + specifier.type !== AST_NODE_TYPES.ImportSpecifier || + specifier.importKind !== 'type' + ) { + return; + } + specifiers.push(specifier); + } + + context.report({ + node, + messageId: 'useTopLevelQualifier', + fix(fixer) { + const fixes: TSESLint.RuleFix[] = []; + for (const specifier of specifiers) { + const qualifier = util.nullThrows( + sourceCode.getFirstToken(specifier, util.isTypeKeyword), + util.NullThrowsReasons.MissingToken( + 'type keyword', + 'import specifier', + ), + ); + fixes.push( + fixer.removeRange([ + qualifier.range[0], + specifier.imported.range[0], + ]), + ); + } + + const importKeyword = util.nullThrows( + sourceCode.getFirstToken(node, util.isImportKeyword), + util.NullThrowsReasons.MissingToken('import keyword', 'import'), + ); + fixes.push(fixer.insertTextAfter(importKeyword, ' type')); + + return fixes; + }, + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index effbed48eaaf..1bc83c07c701 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -147,7 +147,7 @@ export default util.createRule({ } case AST_NODE_TYPES.TSNullKeyword: - return init.type === AST_NODE_TYPES.Literal && init.value === null; + return init.type === AST_NODE_TYPES.Literal && init.value == null; case AST_NODE_TYPES.TSStringKeyword: return ( diff --git a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts index 7b9492972e8e..2c0d84364e4b 100644 --- a/packages/eslint-plugin/src/rules/no-loss-of-precision.ts +++ b/packages/eslint-plugin/src/rules/no-loss-of-precision.ts @@ -25,7 +25,7 @@ export default util.createRule({ }, defaultOptions: [], create(context) { - /* istanbul ignore if */ if (baseRule === null) { + /* istanbul ignore if */ if (baseRule == null) { throw new Error( '@typescript-eslint/no-loss-of-precision requires at least ESLint v7.1.0', ); diff --git a/packages/eslint-plugin/src/rules/no-misused-promises.ts b/packages/eslint-plugin/src/rules/no-misused-promises.ts index b6914ae2c392..15bf0c501d15 100644 --- a/packages/eslint-plugin/src/rules/no-misused-promises.ts +++ b/packages/eslint-plugin/src/rules/no-misused-promises.ts @@ -250,7 +250,7 @@ export default util.createRule({ function checkVariableDeclaration(node: TSESTree.VariableDeclarator): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - if (tsNode.initializer === undefined || node.init === null) { + if (tsNode.initializer === undefined || node.init == null) { return; } const varType = checker.getTypeAtLocation(tsNode.name); @@ -344,7 +344,7 @@ export default util.createRule({ function checkReturnStatement(node: TSESTree.ReturnStatement): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - if (tsNode.expression === undefined || node.argument === null) { + if (tsNode.expression === undefined || node.argument == null) { return; } const contextualType = checker.getContextualType(tsNode.expression); @@ -368,7 +368,7 @@ export default util.createRule({ const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const value = tsNode.initializer; if ( - node.value === null || + node.value == null || value === undefined || !ts.isJsxExpression(value) || value.expression === undefined diff --git a/packages/eslint-plugin/src/rules/no-non-null-asserted-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/no-non-null-asserted-nullish-coalescing.ts index 8706703c9bab..a79fa4062b1f 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-asserted-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-asserted-nullish-coalescing.ts @@ -27,7 +27,7 @@ function isDefinitionWithAssignment(definition: Definition): boolean { const variableDeclarator = definition.node; return ( - variableDeclarator.definite === true || variableDeclarator.init !== null + variableDeclarator.definite === true || variableDeclarator.init != null ); } diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 5a6872f57484..42f12748af90 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -384,7 +384,7 @@ export default createRule({ | TSESTree.ForStatement | TSESTree.WhileStatement, ): void { - if (node.test === null) { + if (node.test == null) { // e.g. `for(;;)` return; } diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index fbf3b41e9668..632ad6c5ba0f 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -53,7 +53,7 @@ export default util.createRule({ const alias = tryGetAliasedSymbol(symbol, checker); - return alias !== null && symbolIsNamespaceInScope(alias); + return alias != null && symbolIsNamespaceInScope(alias); } function getSymbolInScope( diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts index 5153ed47fcd8..b88cd82d39d7 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -21,7 +21,7 @@ function parseOptions(options: string | Config | null): Required { if (typeof options === 'string') { functions = options !== 'nofunc'; - } else if (typeof options === 'object' && options !== null) { + } else if (typeof options === 'object' && options != null) { functions = options.functions !== false; classes = options.classes !== false; enums = options.enums !== false; @@ -64,7 +64,7 @@ function isOuterEnum( reference: TSESLint.Scope.Reference, ): boolean { return ( - variable.defs[0].type == DefinitionType.TSEnumName && + variable.defs[0].type === DefinitionType.TSEnumName && variable.scope.variableScope !== reference.from.variableScope ); } diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 9bb8802a7da7..ddde074a234d 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -24,8 +24,7 @@ export default util.createRule({ node: TSESTree.Node | null, ): node is TSESTree.VariableDeclaration { return ( - node !== null && - node.type === AST_NODE_TYPES.VariableDeclaration && + node?.type === AST_NODE_TYPES.VariableDeclaration && node.kind !== 'const' && node.declarations.length === 1 ); @@ -39,7 +38,7 @@ export default util.createRule({ } function isZeroInitialized(node: TSESTree.VariableDeclarator): boolean { - return node.init !== null && isLiteral(node.init, 0); + return node.init != null && isLiteral(node.init, 0); } function isMatchingIdentifier( @@ -54,8 +53,7 @@ export default util.createRule({ name: string, ): TSESTree.Expression | null { if ( - node !== null && - node.type === AST_NODE_TYPES.BinaryExpression && + node?.type === AST_NODE_TYPES.BinaryExpression && node.operator === '<' && isMatchingIdentifier(node.left, name) && node.right.type === AST_NODE_TYPES.MemberExpression && diff --git a/packages/eslint-plugin/src/rules/prefer-function-type.ts b/packages/eslint-plugin/src/rules/prefer-function-type.ts index 95b3ee5d33b7..db5dde69d001 100644 --- a/packages/eslint-plugin/src/rules/prefer-function-type.ts +++ b/packages/eslint-plugin/src/rules/prefer-function-type.ts @@ -82,8 +82,7 @@ export default util.createRule({ typeof member.returnType !== 'undefined' ) { if ( - tsThisTypes !== null && - tsThisTypes.length > 0 && + tsThisTypes?.length && node.type === AST_NODE_TYPES.TSInterfaceDeclaration ) { // the message can be confusing if we don't point directly to the `this` node instead of the whole member @@ -205,7 +204,7 @@ export default util.createRule({ // inside an interface keep track of all ThisType references. // unless it's inside a nested type literal in which case it's invalid code anyway // we don't want to incorrectly say "it refers to name" while typescript says it's completely invalid. - if (literalNesting === 0 && tsThisTypes !== null) { + if (literalNesting === 0 && tsThisTypes != null) { tsThisTypes.push(node); } }, diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 9c50ce118f80..720d5fbe8092 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -38,7 +38,7 @@ export default createRule({ function isNumber(node: TSESTree.Node, value: number): boolean { const evaluated = getStaticValue(node, globalScope); - return evaluated !== null && evaluated.value === value; + return evaluated != null && evaluated.value === value; } function isPositiveCheck(node: TSESTree.BinaryExpression): boolean { diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 13452d48f86f..60bf310947fa 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -123,7 +123,7 @@ export default createRule({ if ( argumentNode.type === AST_NODE_TYPES.Literal && - typeof argumentNode.value == 'string' + typeof argumentNode.value === 'string' ) { const regExp = RegExp(argumentNode.value); return context.report({ diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 31a570652dcb..104637062bb6 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -60,7 +60,7 @@ export default createRule({ */ function isNull(node: TSESTree.Node): node is TSESTree.Literal { const evaluated = getStaticValue(node, globalScope); - return evaluated != null && evaluated.value === null; + return evaluated != null && evaluated.value == null; } /** diff --git a/packages/eslint-plugin/src/rules/space-before-function-paren.ts b/packages/eslint-plugin/src/rules/space-before-function-paren.ts index 4a3f9042e784..5ff33aeb4fed 100644 --- a/packages/eslint-plugin/src/rules/space-before-function-paren.ts +++ b/packages/eslint-plugin/src/rules/space-before-function-paren.ts @@ -140,7 +140,8 @@ export default util.createRule({ return; } - let leftToken: TSESTree.Token, rightToken: TSESTree.Token; + let leftToken: TSESTree.Token; + let rightToken: TSESTree.Token; if (node.typeParameters) { leftToken = sourceCode.getLastToken(node.typeParameters)!; rightToken = sourceCode.getTokenAfter(leftToken)!; diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts index 1e327a8a4b83..bf58727df296 100644 --- a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -13,6 +13,7 @@ export type Options = [ allowNullableBoolean?: boolean; allowNullableString?: boolean; allowNullableNumber?: boolean; + allowNullableEnum?: boolean; allowAny?: boolean; allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean; }, @@ -29,6 +30,7 @@ export type MessageId = | 'conditionErrorNullableNumber' | 'conditionErrorObject' | 'conditionErrorNullableObject' + | 'conditionErrorNullableEnum' | 'noStrictNullCheck' | 'conditionFixDefaultFalse' | 'conditionFixDefaultEmptyString' @@ -63,6 +65,7 @@ export default util.createRule({ allowNullableBoolean: { type: 'boolean' }, allowNullableString: { type: 'boolean' }, allowNullableNumber: { type: 'boolean' }, + allowNullableEnum: { type: 'boolean' }, allowAny: { type: 'boolean' }, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: { type: 'boolean', @@ -102,6 +105,9 @@ export default util.createRule({ conditionErrorNullableObject: 'Unexpected nullable object value in conditional. ' + 'An explicit null check is required.', + conditionErrorNullableEnum: + 'Unexpected nullable enum value in conditional. ' + + 'Please handle the nullish/zero/NaN cases explicitly.', noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.', @@ -137,6 +143,7 @@ export default util.createRule({ allowNullableBoolean: false, allowNullableString: false, allowNullableNumber: false, + allowNullableEnum: true, allowAny: false, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false, }, @@ -718,6 +725,35 @@ export default util.createRule({ return; } + // nullable enum + if (is('nullish', 'number', 'enum') || is('nullish', 'string', 'enum')) { + if (!options.allowNullableEnum) { + if (isLogicalNegationExpression(node.parent!)) { + context.report({ + node, + messageId: 'conditionErrorNullableEnum', + fix: util.getWrappingFixer({ + sourceCode, + node: node.parent, + innerNode: node, + wrap: code => `${code} == null`, + }), + }); + } else { + context.report({ + node, + messageId: 'conditionErrorNullableEnum', + fix: util.getWrappingFixer({ + sourceCode, + node, + wrap: code => `${code} != null`, + }), + }); + } + } + return; + } + // any if (is('any')) { if (!options.allowAny) { @@ -753,6 +789,7 @@ export default util.createRule({ | 'number' | 'truthy number' | 'object' + | 'enum' | 'any' | 'never'; @@ -814,6 +851,12 @@ export default util.createRule({ } } + if ( + types.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.EnumLike)) + ) { + variantTypes.add('enum'); + } + if ( types.some( type => diff --git a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts index cff8960dac84..43d4913b4ca3 100644 --- a/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts +++ b/packages/eslint-plugin/src/rules/switch-exhaustiveness-check.ts @@ -121,7 +121,7 @@ export default createRule({ const unionTypes = unionTypeParts(discriminantType); const caseTypes: Set = new Set(); for (const switchCase of node.cases) { - if (switchCase.test === null) { + if (switchCase.test == null) { // Switch has 'default' branch - do nothing. return; } diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts index 5780d55cb5c2..4425e666338d 100644 --- a/packages/eslint-plugin/src/rules/triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -87,7 +87,7 @@ export default util.createRule({ } }, Program(node): void { - if (lib === 'always' && path === 'always' && types == 'always') { + if (lib === 'always' && path === 'always' && types === 'always') { return; } programNode = node; diff --git a/packages/eslint-plugin/src/util/isNullLiteral.ts b/packages/eslint-plugin/src/util/isNullLiteral.ts index f8695f260924..85bf45882123 100644 --- a/packages/eslint-plugin/src/util/isNullLiteral.ts +++ b/packages/eslint-plugin/src/util/isNullLiteral.ts @@ -2,5 +2,5 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; export function isNullLiteral(i: TSESTree.Node): boolean { - return i.type === AST_NODE_TYPES.Literal && i.value === null; + return i.type === AST_NODE_TYPES.Literal && i.value == null; } diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index fa9c5ccf5287..8362736bd628 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -210,6 +210,7 @@ function typeNodeRequiresParentheses( return ( node.type === AST_NODE_TYPES.TSFunctionType || node.type === AST_NODE_TYPES.TSConstructorType || + node.type === AST_NODE_TYPES.TSConditionalType || (node.type === AST_NODE_TYPES.TSUnionType && text.startsWith('|')) || (node.type === AST_NODE_TYPES.TSIntersectionType && text.startsWith('&')) ); diff --git a/packages/eslint-plugin/tests/rules/indent/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts index 65fe9240377b..f9191d3ef105 100644 --- a/packages/eslint-plugin/tests/rules/indent/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -638,7 +638,7 @@ type Foo = string | { }) .filter( (error): error is TSESLint.TestCaseError => - error !== null, + error != null, ), }; if (invalid.errors.length > 0) { diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts index a80acab989ae..070cef91e9a6 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -405,6 +405,57 @@ void doSomething(); `, options: [{ ignoreIIFE: true }], }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + void (condition && myPromise()); +} + `, + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + await (condition && myPromise()); +} + `, + options: [{ ignoreVoid: false }], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + condition && void myPromise(); +} + `, + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + condition && (await myPromise()); +} + `, + options: [{ ignoreVoid: false }], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + let condition = false; + condition && myPromise(); + condition = true; + condition || myPromise(); + condition ?? myPromise(); +} + `, + options: [{ ignoreVoid: false }], + }, ], invalid: [ @@ -1117,5 +1168,264 @@ async function test() { }, ], }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + + void condition || myPromise(); +} + `, + errors: [ + { + line: 6, + messageId: 'floatingVoid', + suggestions: [ + { + messageId: 'floatingFixVoid', + output: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + + void (void condition || myPromise()); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + + (await condition) && myPromise(); +} + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 6, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + + await ((await condition) && myPromise()); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + + condition && myPromise(); +} + `, + errors: [ + { + line: 6, + messageId: 'floatingVoid', + suggestions: [ + { + messageId: 'floatingFixVoid', + output: ` +async function foo() { + const myPromise = async () => void 0; + const condition = true; + + void (condition && myPromise()); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = false; + + condition || myPromise(); +} + `, + errors: [ + { + line: 6, + messageId: 'floatingVoid', + suggestions: [ + { + messageId: 'floatingFixVoid', + output: ` +async function foo() { + const myPromise = async () => void 0; + const condition = false; + + void (condition || myPromise()); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = null; + + condition ?? myPromise(); +} + `, + errors: [ + { + line: 6, + messageId: 'floatingVoid', + suggestions: [ + { + messageId: 'floatingFixVoid', + output: ` +async function foo() { + const myPromise = async () => void 0; + const condition = null; + + void (condition ?? myPromise()); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = Promise.resolve(true); + let condition = true; + condition && myPromise; +} + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function foo() { + const myPromise = Promise.resolve(true); + let condition = true; + await (condition && myPromise); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = Promise.resolve(true); + let condition = false; + condition || myPromise; +} + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function foo() { + const myPromise = Promise.resolve(true); + let condition = false; + await (condition || myPromise); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = Promise.resolve(true); + let condition = null; + condition ?? myPromise; +} + `, + options: [{ ignoreVoid: false }], + errors: [ + { + line: 5, + messageId: 'floating', + suggestions: [ + { + messageId: 'floatingFixAwait', + output: ` +async function foo() { + const myPromise = Promise.resolve(true); + let condition = null; + await (condition ?? myPromise); +} + `, + }, + ], + }, + ], + }, + { + code: ` +async function foo() { + const myPromise = async () => void 0; + const condition = false; + + condition || condition || myPromise(); +} + `, + errors: [ + { + line: 6, + messageId: 'floatingVoid', + suggestions: [ + { + messageId: 'floatingFixVoid', + output: ` +async function foo() { + const myPromise = async () => void 0; + const condition = false; + + void (condition || condition || myPromise()); +} + `, + }, + ], + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-import-type-side-effects.test.ts b/packages/eslint-plugin/tests/rules/no-import-type-side-effects.test.ts new file mode 100644 index 000000000000..9dade06a9432 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-import-type-side-effects.test.ts @@ -0,0 +1,44 @@ +import rule from '../../src/rules/no-import-type-side-effects'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-import-type-side-effects', rule, { + valid: [ + "import T from 'mod';", + "import * as T from 'mod';", + "import { T } from 'mod';", + "import type { T } from 'mod';", + "import type { T, U } from 'mod';", + "import { type T, U } from 'mod';", + "import { T, type U } from 'mod';", + "import type T from 'mod';", + "import type T, { U } from 'mod';", + "import T, { type U } from 'mod';", + "import type * as T from 'mod';", + ], + invalid: [ + { + code: "import { type A } from 'mod';", + output: "import type { A } from 'mod';", + errors: [{ messageId: 'useTopLevelQualifier' }], + }, + { + code: "import { type A as AA } from 'mod';", + output: "import type { A as AA } from 'mod';", + errors: [{ messageId: 'useTopLevelQualifier' }], + }, + { + code: "import { type A, type B } from 'mod';", + output: "import type { A, B } from 'mod';", + errors: [{ messageId: 'useTopLevelQualifier' }], + }, + { + code: "import { type A as AA, type B as BB } from 'mod';", + output: "import type { A as AA, B as BB } from 'mod';", + errors: [{ messageId: 'useTopLevelQualifier' }], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/prefer-string-starts-ends-with.test.ts b/packages/eslint-plugin/tests/rules/prefer-string-starts-ends-with.test.ts index 82a25e2f472e..c9fe331c99d7 100644 --- a/packages/eslint-plugin/tests/rules/prefer-string-starts-ends-with.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-string-starts-ends-with.test.ts @@ -1083,7 +1083,7 @@ function addOptional< function makeOptional(code: string): string; function makeOptional(code: string | null | undefined): string | null; function makeOptional(code: string | null | undefined): string | null { - if (code === null || code === undefined) { + if (code == null) { return null; } return ( diff --git a/packages/eslint-plugin/tests/rules/sort-type-constituents.test.ts b/packages/eslint-plugin/tests/rules/sort-type-constituents.test.ts index 1aa6f8a6c9a3..42f9ab8153a3 100644 --- a/packages/eslint-plugin/tests/rules/sort-type-constituents.test.ts +++ b/packages/eslint-plugin/tests/rules/sort-type-constituents.test.ts @@ -359,6 +359,7 @@ type T = 1 | string | {} | A; }, ], }, + "type A = string | (T extends number ? 'hi' : 'there');", ], invalid: [ ...invalid('|'), @@ -376,5 +377,18 @@ type T = 1 | string | {} | A; }, ], }, + { + output: "type A = string | (T extends number ? 'hi' : 'there');", + code: "type A = (T extends number ? 'hi' : 'there') | string;", + errors: [ + { + messageId: 'notSortedNamed', + data: { + type: 'Union', + name: 'A', + }, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts index 9261acda41b7..f3b6deef1bf5 100644 --- a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts @@ -133,6 +133,39 @@ ruleTester.run('strict-boolean-expressions', rule, { `, }), + // nullable enum in boolean context + { + code: ` + enum ExampleEnum { + This = 0, + That = 1, + } + const rand = Math.random(); + let theEnum: ExampleEnum | null = null; + if (rand < 0.3) { + theEnum = ExampleEnum.This; + } + if (theEnum) { + } + `, + options: [{ allowNullableEnum: true }], + }, + { + code: ` + enum ExampleEnum { + This = 0, + That = 1, + } + const rand = Math.random(); + let theEnum: ExampleEnum | null = null; + if (rand < 0.3) { + theEnum = ExampleEnum.This; + } + if (!theEnum) { + } + `, + options: [{ allowNullableEnum: true }], + }, { code: ` declare const x: string[] | null; @@ -965,6 +998,157 @@ if (y) { ], }), + // nullable enum in boolean context + { + options: [{ allowNullableEnum: false }], + code: ` + enum ExampleEnum { + This = 0, + That = 1, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (theEnum) { + } + `, + errors: [ + { + line: 7, + column: 13, + messageId: 'conditionErrorNullableEnum', + endLine: 7, + endColumn: 20, + }, + ], + output: ` + enum ExampleEnum { + This = 0, + That = 1, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (theEnum != null) { + } + `, + }, + { + options: [{ allowNullableEnum: false }], + code: ` + enum ExampleEnum { + This = 0, + That = 1, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (!theEnum) { + } + `, + errors: [ + { + line: 7, + column: 14, + messageId: 'conditionErrorNullableEnum', + endLine: 7, + endColumn: 21, + }, + ], + output: ` + enum ExampleEnum { + This = 0, + That = 1, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (theEnum == null) { + } + `, + }, + { + options: [{ allowNullableEnum: false }], + code: ` + enum ExampleEnum { + This, + That, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (!theEnum) { + } + `, + errors: [ + { + line: 7, + column: 14, + messageId: 'conditionErrorNullableEnum', + endLine: 7, + endColumn: 21, + }, + ], + output: ` + enum ExampleEnum { + This, + That, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (theEnum == null) { + } + `, + }, + { + options: [{ allowNullableEnum: false }], + code: ` + enum ExampleEnum { + This = '', + That = 'a', + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (!theEnum) { + } + `, + errors: [ + { + line: 7, + column: 14, + messageId: 'conditionErrorNullableEnum', + endLine: 7, + endColumn: 21, + }, + ], + output: ` + enum ExampleEnum { + This = '', + That = 'a', + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (theEnum == null) { + } + `, + }, + { + options: [{ allowNullableEnum: false }], + code: ` + enum ExampleEnum { + This = '', + That = 0, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (!theEnum) { + } + `, + errors: [ + { + line: 7, + column: 14, + messageId: 'conditionErrorNullableEnum', + endLine: 7, + endColumn: 21, + }, + ], + output: ` + enum ExampleEnum { + This = '', + That = 0, + } + const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null; + if (theEnum == null) { + } + `, + }, // any in boolean context ...batchedSingleLineTests({ code: noFormat` diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index a11333dc15c7..ddc32b003c5b 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/experimental-utils diff --git a/packages/experimental-utils/README.md b/packages/experimental-utils/README.md index d1468f928b15..a285229f6389 100644 --- a/packages/experimental-utils/README.md +++ b/packages/experimental-utils/README.md @@ -19,4 +19,4 @@ You should switch to importing from that non-experimental package instead. ## Contributing -[See the contributing guide here](../../CONTRIBUTING.md) +[See the contributing guide here](https://typescript-eslint.io). diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index e3d44a14dfec..361143560976 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "5.50.0", + "version": "5.51.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -38,7 +38,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/utils": "5.50.0" + "@typescript-eslint/utils": "5.51.0" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 8dd79a4e7d19..b6d0b1016f3b 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/parser + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index 72b9df7a210c..e2bddc8fca28 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "5.50.0", + "version": "5.51.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -45,9 +45,9 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "5.50.0", - "@typescript-eslint/types": "5.50.0", - "@typescript-eslint/typescript-estree": "5.50.0", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 4243c56c73e5..c19e0b075295 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/scope-manager + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/scope-manager diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index afa72f7662a5..c600e4ff2fd7 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "5.50.0", + "version": "5.51.0", "description": "TypeScript scope analyser for ESLint", "keywords": [ "eslint", @@ -38,12 +38,12 @@ "typecheck": "nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "5.50.0", - "@typescript-eslint/visitor-keys": "5.50.0" + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0" }, "devDependencies": { "@types/glob": "*", - "@typescript-eslint/typescript-estree": "5.50.0", + "@typescript-eslint/typescript-estree": "5.51.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/scope-manager/src/ScopeManager.ts b/packages/scope-manager/src/ScopeManager.ts index 5368cca1dc3b..7f4b2a5f7052 100644 --- a/packages/scope-manager/src/ScopeManager.ts +++ b/packages/scope-manager/src/ScopeManager.ts @@ -140,7 +140,7 @@ class ScopeManager { protected nestScope(scope: T): T; protected nestScope(scope: Scope): Scope { if (scope instanceof GlobalScope) { - assert(this.currentScope === null); + assert(this.currentScope == null); this.globalScope = scope; } this.currentScope = scope; diff --git a/packages/scope-manager/src/referencer/ClassVisitor.ts b/packages/scope-manager/src/referencer/ClassVisitor.ts index 5f84e37404f3..662b70813c83 100644 --- a/packages/scope-manager/src/referencer/ClassVisitor.ts +++ b/packages/scope-manager/src/referencer/ClassVisitor.ts @@ -163,7 +163,7 @@ class ClassVisitor extends Visitor { * } */ if ( - keyName !== null && + keyName != null && this.#classNode.body.body.find( (node): node is TSESTree.MethodDefinition => node !== methodNode && diff --git a/packages/scope-manager/src/referencer/PatternVisitor.ts b/packages/scope-manager/src/referencer/PatternVisitor.ts index 308c4c29208a..53de28469e85 100644 --- a/packages/scope-manager/src/referencer/PatternVisitor.ts +++ b/packages/scope-manager/src/referencer/PatternVisitor.ts @@ -97,10 +97,7 @@ class PatternVisitor extends VisitorBase { this.#callback(pattern, { topLevel: pattern === this.#rootPattern, - rest: - lastRestElement !== null && - lastRestElement !== undefined && - lastRestElement.argument === pattern, + rest: lastRestElement != null && lastRestElement.argument === pattern, assignments: this.#assignments, }); } diff --git a/packages/scope-manager/src/referencer/Referencer.ts b/packages/scope-manager/src/referencer/Referencer.ts index e7b41127ba46..7a14de51df15 100644 --- a/packages/scope-manager/src/referencer/Referencer.ts +++ b/packages/scope-manager/src/referencer/Referencer.ts @@ -124,7 +124,7 @@ class Referencer extends Visitor { } private referenceJsxPragma(): void { - if (this.#jsxPragma === null || this.#hasReferencedJsxFactory) { + if (this.#jsxPragma == null || this.#hasReferencedJsxFactory) { return; } this.#hasReferencedJsxFactory = this.referenceInSomeUpperScope( @@ -134,7 +134,7 @@ class Referencer extends Visitor { private referenceJsxFragment(): void { if ( - this.#jsxFragmentName === null || + this.#jsxFragmentName == null || this.#hasReferencedJsxFragmentFactory ) { return; diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 3548d5254c6d..62cf64f412a2 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 0e3d35976d45..c6bd1ccd6645 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,6 +1,6 @@ { "description": "Code fixtures used to test the typescript-estree parser.", "name": "@typescript-eslint/shared-fixtures", - "version": "5.50.0", + "version": "5.51.0", "private": true } diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index 26f418c1436c..1c6a2a15d959 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/type-utils + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/type-utils diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index 2b44585a6821..f4354a5342b6 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "5.50.0", + "version": "5.51.0", "description": "Type utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -39,13 +39,13 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "5.50.0", - "@typescript-eslint/utils": "5.50.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/utils": "5.51.0", "debug": "^4.3.4", "tsutils": "^3.21.0" }, "devDependencies": { - "@typescript-eslint/parser": "5.50.0", + "@typescript-eslint/parser": "5.51.0", "typescript": "*" }, "peerDependencies": { diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 0cd6d24aa095..497e08356510 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + + +### Features + +* **typescript-estree:** cache project glob resolution ([#6367](https://github.com/typescript-eslint/typescript-eslint/issues/6367)) ([afae837](https://github.com/typescript-eslint/typescript-eslint/commit/afae8374df64101627808ccfeb5b715c865e910f)) + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/types diff --git a/packages/types/package.json b/packages/types/package.json index ccc72bcd6986..fd48c6e4466a 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "5.50.0", + "version": "5.51.0", "description": "Types for the TypeScript-ESTree AST spec", "keywords": [ "eslint", diff --git a/packages/types/src/parser-options.ts b/packages/types/src/parser-options.ts index b3149231215e..a7fe3ce3ab36 100644 --- a/packages/types/src/parser-options.ts +++ b/packages/types/src/parser-options.ts @@ -3,6 +3,7 @@ import type { Program } from 'typescript'; import type { Lib } from './lib'; type DebugLevel = boolean | ('typescript-eslint' | 'eslint' | 'typescript')[]; +type CacheDurationSeconds = number | 'Infinity'; type EcmaVersion = | 3 @@ -59,7 +60,17 @@ interface ParserOptions { tsconfigRootDir?: string; warnOnUnsupportedTypeScriptVersion?: boolean; moduleResolver?: string; + cacheLifetime?: { + glob?: CacheDurationSeconds; + }; + [additionalProperties: string]: unknown; } -export { DebugLevel, EcmaVersion, ParserOptions, SourceType }; +export { + CacheDurationSeconds, + DebugLevel, + EcmaVersion, + ParserOptions, + SourceType, +}; diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index b74f9d0a82db..8d1eb8e9a854 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + + +### Features + +* **typescript-estree:** cache project glob resolution ([#6367](https://github.com/typescript-eslint/typescript-eslint/issues/6367)) ([afae837](https://github.com/typescript-eslint/typescript-eslint/commit/afae8374df64101627808ccfeb5b715c865e910f)) + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/typescript-estree diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 728c8d417e96..22afddb5fc2c 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "5.50.0", + "version": "5.51.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/index.js", "types": "dist/index.d.ts", @@ -42,8 +42,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "5.50.0", - "@typescript-eslint/visitor-keys": "5.50.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -59,7 +59,7 @@ "@types/is-glob": "*", "@types/semver": "*", "@types/tmp": "*", - "@typescript-eslint/shared-fixtures": "5.50.0", + "@typescript-eslint/shared-fixtures": "5.51.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/typescript-estree/src/convert-comments.ts b/packages/typescript-estree/src/convert-comments.ts index ea02be412768..d4dd9f124a79 100644 --- a/packages/typescript-estree/src/convert-comments.ts +++ b/packages/typescript-estree/src/convert-comments.ts @@ -22,7 +22,7 @@ export function convertComments( ast, (_, comment) => { const type = - comment.kind == ts.SyntaxKind.SingleLineCommentTrivia + comment.kind === ts.SyntaxKind.SingleLineCommentTrivia ? AST_TOKEN_TYPES.Line : AST_TOKEN_TYPES.Block; const range: TSESTree.Range = [comment.pos, comment.end]; diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index b21a42614dae..70ee9da4e0ce 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -2183,7 +2183,7 @@ export class Converter { type: AST_NODE_TYPES.Literal, raw: rawValue, value: value, - bigint: value === null ? bigint : String(value), + bigint: value == null ? bigint : String(value), range, }); } diff --git a/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts b/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts index 15d88e5f4540..d9d4de9c833f 100644 --- a/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts +++ b/packages/typescript-estree/src/create-program/getWatchProgramsForProjects.ts @@ -8,6 +8,7 @@ import type { CanonicalPath } from './shared'; import { canonicalDirname, createDefaultCompilerOptionsFromExtra, + createHash, getCanonicalFileName, getModuleResolver, } from './shared'; @@ -105,19 +106,6 @@ function diagnosticReporter(diagnostic: ts.Diagnostic): void { ); } -/** - * Hash content for compare content. - * @param content hashed contend - * @returns hashed result - */ -function createHash(content: string): string { - // No ts.sys in browser environments. - if (ts.sys?.createHash) { - return ts.sys.createHash(content); - } - return content; -} - function updateCachedFileList( tsconfigPath: CanonicalPath, program: ts.Program, diff --git a/packages/typescript-estree/src/create-program/shared.ts b/packages/typescript-estree/src/create-program/shared.ts index dd50f757dce1..e8de97969283 100644 --- a/packages/typescript-estree/src/create-program/shared.ts +++ b/packages/typescript-estree/src/create-program/shared.ts @@ -124,12 +124,26 @@ function getModuleResolver(moduleResolverPath: string): ModuleResolver { return moduleResolver; } +/** + * Hash content for compare content. + * @param content hashed contend + * @returns hashed result + */ +function createHash(content: string): string { + // No ts.sys in browser environments. + if (ts.sys?.createHash) { + return ts.sys.createHash(content); + } + return content; +} + export { ASTAndProgram, CORE_COMPILER_OPTIONS, canonicalDirname, CanonicalPath, createDefaultCompilerOptionsFromExtra, + createHash, ensureAbsolutePath, getCanonicalFileName, getAstFromProgram, diff --git a/packages/typescript-estree/src/getModifiers.ts b/packages/typescript-estree/src/getModifiers.ts index d8f8e716f9e2..a584a7659a77 100644 --- a/packages/typescript-estree/src/getModifiers.ts +++ b/packages/typescript-estree/src/getModifiers.ts @@ -31,7 +31,7 @@ export function getModifiers( export function getDecorators( node: ts.Node | null | undefined, ): undefined | ts.Decorator[] { - if (node == undefined) { + if (node == null) { return undefined; } diff --git a/packages/typescript-estree/src/parseSettings/ExpiringCache.ts b/packages/typescript-estree/src/parseSettings/ExpiringCache.ts new file mode 100644 index 000000000000..f296c9f5f590 --- /dev/null +++ b/packages/typescript-estree/src/parseSettings/ExpiringCache.ts @@ -0,0 +1,69 @@ +import type { CacheDurationSeconds } from '@typescript-eslint/types'; + +export const DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS = 30; +const ZERO_HR_TIME: [number, number] = [0, 0]; + +/** + * A map with key-level expiration. + */ +export class ExpiringCache { + readonly #cacheDurationSeconds: CacheDurationSeconds; + /** + * The mapping of path-like string to the resolved TSConfig(s) + */ + protected readonly map = new Map< + TKey, + Readonly<{ + value: TValue; + lastSeen: [number, number]; + }> + >(); + + constructor(cacheDurationSeconds: CacheDurationSeconds) { + this.#cacheDurationSeconds = cacheDurationSeconds; + } + + set(key: TKey, value: TValue): this { + this.map.set(key, { + value, + lastSeen: + this.#cacheDurationSeconds === 'Infinity' + ? // no need to waste time calculating the hrtime in infinity mode as there's no expiry + ZERO_HR_TIME + : process.hrtime(), + }); + return this; + } + + get(key: TKey): TValue | undefined { + const entry = this.map.get(key); + if (entry?.value != null) { + if (this.#cacheDurationSeconds === 'Infinity') { + return entry.value; + } + + const ageSeconds = process.hrtime(entry.lastSeen)[0]; + if (ageSeconds < this.#cacheDurationSeconds) { + // cache hit woo! + return entry.value; + } else { + // key has expired - clean it up to free up memory + this.cleanupKey(key); + } + } + // no hit :'( + return undefined; + } + + protected cleanupKey(key: TKey): void { + this.map.delete(key); + } + + get size(): number { + return this.map.size; + } + + clear(): void { + this.map.clear(); + } +} diff --git a/packages/typescript-estree/src/parseSettings/createParseSettings.ts b/packages/typescript-estree/src/parseSettings/createParseSettings.ts index b1cde9d4c9ad..e7267a852686 100644 --- a/packages/typescript-estree/src/parseSettings/createParseSettings.ts +++ b/packages/typescript-estree/src/parseSettings/createParseSettings.ts @@ -1,15 +1,10 @@ import debug from 'debug'; -import { sync as globSync } from 'globby'; -import isGlob from 'is-glob'; -import type { CanonicalPath } from '../create-program/shared'; -import { - ensureAbsolutePath, - getCanonicalFileName, -} from '../create-program/shared'; +import { ensureAbsolutePath } from '../create-program/shared'; import type { TSESTreeOptions } from '../parser-options'; import type { MutableParseSettings } from './index'; import { inferSingleRun } from './inferSingleRun'; +import { resolveProjectList } from './resolveProjectList'; import { warnAboutTSVersion } from './warnAboutTSVersion'; const log = debug( @@ -98,23 +93,13 @@ export function createParseSettings( // Providing a program overrides project resolution if (!parseSettings.programs) { - const projectFolderIgnoreList = ( - options.projectFolderIgnoreList ?? ['**/node_modules/**'] - ) - .reduce((acc, folder) => { - if (typeof folder === 'string') { - acc.push(folder); - } - return acc; - }, []) - // prefix with a ! for not match glob - .map(folder => (folder.startsWith('!') ? folder : `!${folder}`)); - - parseSettings.projects = prepareAndTransformProjects( - tsconfigRootDir, - options.project, - projectFolderIgnoreList, - ); + parseSettings.projects = resolveProjectList({ + cacheLifetime: options.cacheLifetime, + project: options.project, + projectFolderIgnoreList: options.projectFolderIgnoreList, + singleRun: parseSettings.singleRun, + tsconfigRootDir: tsconfigRootDir, + }); } warnAboutTSVersion(parseSettings); @@ -144,58 +129,3 @@ function enforceString(code: unknown): string { function getFileName(jsx?: boolean): string { return jsx ? 'estree.tsx' : 'estree.ts'; } - -function getTsconfigPath( - tsconfigPath: string, - tsconfigRootDir: string, -): CanonicalPath { - return getCanonicalFileName( - ensureAbsolutePath(tsconfigPath, tsconfigRootDir), - ); -} - -/** - * Normalizes, sanitizes, resolves and filters the provided project paths - */ -function prepareAndTransformProjects( - tsconfigRootDir: string, - projectsInput: string | string[] | undefined, - ignoreListInput: string[], -): CanonicalPath[] { - const sanitizedProjects: string[] = []; - - // Normalize and sanitize the project paths - if (typeof projectsInput === 'string') { - sanitizedProjects.push(projectsInput); - } else if (Array.isArray(projectsInput)) { - for (const project of projectsInput) { - if (typeof project === 'string') { - sanitizedProjects.push(project); - } - } - } - - if (sanitizedProjects.length === 0) { - return []; - } - - // Transform glob patterns into paths - const nonGlobProjects = sanitizedProjects.filter(project => !isGlob(project)); - const globProjects = sanitizedProjects.filter(project => isGlob(project)); - const uniqueCanonicalProjectPaths = new Set( - nonGlobProjects - .concat( - globSync([...globProjects, ...ignoreListInput], { - cwd: tsconfigRootDir, - }), - ) - .map(project => getTsconfigPath(project, tsconfigRootDir)), - ); - - log( - 'parserOptions.project (excluding ignored) matched projects: %s', - uniqueCanonicalProjectPaths, - ); - - return Array.from(uniqueCanonicalProjectPaths); -} diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 0a9734d1b241..53b1acf4a6fb 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -98,7 +98,7 @@ export interface MutableParseSettings { /** * Normalized paths to provided project paths. */ - projects: CanonicalPath[]; + projects: readonly CanonicalPath[]; /** * Whether to add the `range` property to AST nodes. diff --git a/packages/typescript-estree/src/parseSettings/resolveProjectList.ts b/packages/typescript-estree/src/parseSettings/resolveProjectList.ts new file mode 100644 index 000000000000..72e9539d2b9b --- /dev/null +++ b/packages/typescript-estree/src/parseSettings/resolveProjectList.ts @@ -0,0 +1,146 @@ +import debug from 'debug'; +import { sync as globSync } from 'globby'; +import isGlob from 'is-glob'; + +import type { CanonicalPath } from '../create-program/shared'; +import { + createHash, + ensureAbsolutePath, + getCanonicalFileName, +} from '../create-program/shared'; +import type { TSESTreeOptions } from '../parser-options'; +import { + DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, + ExpiringCache, +} from './ExpiringCache'; + +const log = debug( + 'typescript-eslint:typescript-estree:parser:parseSettings:resolveProjectList', +); + +let RESOLUTION_CACHE: ExpiringCache | null = + null; + +/** + * Normalizes, sanitizes, resolves and filters the provided project paths + */ +export function resolveProjectList( + options: Readonly<{ + cacheLifetime?: TSESTreeOptions['cacheLifetime']; + project: TSESTreeOptions['project']; + projectFolderIgnoreList: TSESTreeOptions['projectFolderIgnoreList']; + singleRun: boolean; + tsconfigRootDir: string; + }>, +): readonly CanonicalPath[] { + const sanitizedProjects: string[] = []; + + // Normalize and sanitize the project paths + if (typeof options.project === 'string') { + sanitizedProjects.push(options.project); + } else if (Array.isArray(options.project)) { + for (const project of options.project) { + if (typeof project === 'string') { + sanitizedProjects.push(project); + } + } + } + + if (sanitizedProjects.length === 0) { + return []; + } + + const projectFolderIgnoreList = ( + options.projectFolderIgnoreList ?? ['**/node_modules/**'] + ) + .reduce((acc, folder) => { + if (typeof folder === 'string') { + acc.push(folder); + } + return acc; + }, []) + // prefix with a ! for not match glob + .map(folder => (folder.startsWith('!') ? folder : `!${folder}`)); + + const cacheKey = getHash({ + project: sanitizedProjects, + projectFolderIgnoreList, + tsconfigRootDir: options.tsconfigRootDir, + }); + if (RESOLUTION_CACHE == null) { + // note - we initialize the global cache based on the first config we encounter. + // this does mean that you can't have multiple lifetimes set per folder + // I doubt that anyone will really bother reconfiguring this, let alone + // try to do complicated setups, so we'll deal with this later if ever. + RESOLUTION_CACHE = new ExpiringCache( + options.singleRun + ? 'Infinity' + : options.cacheLifetime?.glob ?? + DEFAULT_TSCONFIG_CACHE_DURATION_SECONDS, + ); + } else { + const cached = RESOLUTION_CACHE.get(cacheKey); + if (cached) { + return cached; + } + } + + // Transform glob patterns into paths + const nonGlobProjects = sanitizedProjects.filter(project => !isGlob(project)); + const globProjects = sanitizedProjects.filter(project => isGlob(project)); + + const uniqueCanonicalProjectPaths = new Set( + nonGlobProjects + .concat( + globProjects.length === 0 + ? [] + : globSync([...globProjects, ...projectFolderIgnoreList], { + cwd: options.tsconfigRootDir, + }), + ) + .map(project => + getCanonicalFileName( + ensureAbsolutePath(project, options.tsconfigRootDir), + ), + ), + ); + + log( + 'parserOptions.project (excluding ignored) matched projects: %s', + uniqueCanonicalProjectPaths, + ); + + const returnValue = Array.from(uniqueCanonicalProjectPaths); + RESOLUTION_CACHE.set(cacheKey, returnValue); + return returnValue; +} + +function getHash({ + project, + projectFolderIgnoreList, + tsconfigRootDir, +}: Readonly<{ + project: readonly string[]; + projectFolderIgnoreList: readonly string[]; + tsconfigRootDir: string; +}>): string { + // create a stable representation of the config + const hashObject = { + tsconfigRootDir, + // the project order does matter and can impact the resolved globs + project, + // the ignore order won't doesn't ever matter + projectFolderIgnoreList: [...projectFolderIgnoreList].sort(), + }; + + return createHash(JSON.stringify(hashObject)); +} + +/** + * Exported for testing purposes only + * @internal + */ +export function clearGlobResolutionCache(): void { + RESOLUTION_CACHE?.clear(); + RESOLUTION_CACHE = null; +} diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 632d9e6ae883..8cfe3c934c2c 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -1,4 +1,7 @@ -import type { DebugLevel } from '@typescript-eslint/types'; +import type { + CacheDurationSeconds, + DebugLevel, +} from '@typescript-eslint/types'; import type * as ts from 'typescript'; import type { TSESTree, TSESTreeToTSNode, TSNode, TSToken } from './ts-estree'; @@ -168,6 +171,25 @@ interface ParseAndGenerateServicesOptions extends ParseOptions { */ allowAutomaticSingleRunInference?: boolean; + /** + * Granular control of the expiry lifetime of our internal caches. + * You can specify the number of seconds as an integer number, or the string + * 'Infinity' if you never want the cache to expire. + * + * By default cache entries will be evicted after 30 seconds, or will persist + * indefinitely if `allowAutomaticSingleRunInference = true` AND the parser + * infers that it is a single run. + */ + cacheLifetime?: { + /** + * Glob resolution for `parserOptions.project` values. + */ + glob?: CacheDurationSeconds; + }; + + /** + * Path to a file exporting a custom `ModuleResolver`. + */ moduleResolver?: string; } diff --git a/packages/typescript-estree/src/simple-traverse.ts b/packages/typescript-estree/src/simple-traverse.ts index 13c763ec228e..2d51cdbe4fa1 100644 --- a/packages/typescript-estree/src/simple-traverse.ts +++ b/packages/typescript-estree/src/simple-traverse.ts @@ -5,7 +5,7 @@ import type { TSESTree } from './ts-estree'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function isValidNode(x: any): x is TSESTree.Node { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return x !== null && typeof x === 'object' && typeof x.type === 'string'; + return x != null && typeof x === 'object' && typeof x.type === 'string'; } function getVisitorKeysForNode( diff --git a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts index 9c4d8a44bcc9..6abab590b77c 100644 --- a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts +++ b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts @@ -477,11 +477,6 @@ tester.addFixturePatternConfig('typescript/expressions', { * @see https://github.com/babel/babel/issues/14613 */ 'instantiation-expression', - /** - * TS 4.9 `satisfies` operator has not been implemented in Babel yet. - * @see https://github.com/babel/babel/pull/14211 - */ - 'satisfies-expression', ], }); diff --git a/packages/typescript-estree/tests/lib/parse.test.ts b/packages/typescript-estree/tests/lib/parse.test.ts index 3e84dd3e069f..0f286efdb191 100644 --- a/packages/typescript-estree/tests/lib/parse.test.ts +++ b/packages/typescript-estree/tests/lib/parse.test.ts @@ -1,10 +1,14 @@ +import type { CacheDurationSeconds } from '@typescript-eslint/types'; import debug from 'debug'; +import * as globbyModule from 'globby'; import { join, resolve } from 'path'; +import type * as typescriptModule from 'typescript'; import * as parser from '../../src'; import * as astConverterModule from '../../src/ast-converter'; import * as sharedParserUtilsModule from '../../src/create-program/shared'; import type { TSESTreeOptions } from '../../src/parser-options'; +import { clearGlobResolutionCache } from '../../src/parseSettings/resolveProjectList'; import { createSnapshotTestBlock } from '../../tools/test-utils'; const FIXTURES_DIR = join(__dirname, '../fixtures/simpleProject'); @@ -38,7 +42,7 @@ jest.mock('../../src/create-program/shared', () => { // Tests in CI by default run with lowercase program file names, // resulting in path.relative results starting with many "../"s jest.mock('typescript', () => { - const ts = jest.requireActual('typescript'); + const ts = jest.requireActual('typescript'); return { ...ts, sys: { @@ -48,10 +52,21 @@ jest.mock('typescript', () => { }; }); +jest.mock('globby', () => { + const globby = jest.requireActual('globby'); + return { + ...globby, + sync: jest.fn(globby.sync), + }; +}); + +const hrtimeSpy = jest.spyOn(process, 'hrtime'); + const astConverterMock = jest.mocked(astConverterModule.astConverter); const createDefaultCompilerOptionsFromExtra = jest.mocked( sharedParserUtilsModule.createDefaultCompilerOptionsFromExtra, ); +const globbySyncMock = jest.mocked(globbyModule.sync); /** * Aligns paths between environments, node for windows uses `\`, for linux and mac uses `/` @@ -63,6 +78,7 @@ function alignErrorPath(error: Error): never { beforeEach(() => { jest.clearAllMocks(); + clearGlobResolutionCache(); }); describe('parseWithNodeMaps()', () => { @@ -749,8 +765,69 @@ describe('parseAndGenerateServices', () => { it('ignores a folder when given a string glob', () => { const ignore = ['**/ignoreme/**']; + // cspell:disable-next-line expect(testParse('ignoreme', ignore)).toThrow(); + // cspell:disable-next-line expect(testParse('includeme', ignore)).not.toThrow(); }); }); + + describe('cacheLifetime', () => { + describe('glob', () => { + function doParse(lifetime: CacheDurationSeconds): void { + parser.parseAndGenerateServices('const x = 1', { + cacheLifetime: { + glob: lifetime, + }, + filePath: join(FIXTURES_DIR, 'file.ts'), + tsconfigRootDir: FIXTURES_DIR, + project: ['./**/tsconfig.json', './**/tsconfig.extra.json'], + }); + } + + it('should cache globs if the lifetime is non-zero', () => { + doParse(30); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(30); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(1); + }); + + it('should not cache globs if the lifetime is zero', () => { + doParse(0); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + doParse(0); + // should call globby again because we specified immediate cache expiry + expect(globbySyncMock).toHaveBeenCalledTimes(2); + }); + + it('should evict the cache if the entry expires', () => { + hrtimeSpy.mockReturnValueOnce([1, 0]); + + doParse(30); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + + // wow so much time has passed + hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); + + doParse(30); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(2); + }); + + it('should infinitely cache if passed Infinity', () => { + hrtimeSpy.mockReturnValueOnce([1, 0]); + + doParse('Infinity'); + expect(globbySyncMock).toHaveBeenCalledTimes(1); + + // wow so much time has passed + hrtimeSpy.mockReturnValueOnce([Number.MAX_VALUE, 0]); + + doParse('Infinity'); + // shouldn't call globby again due to the caching + expect(globbySyncMock).toHaveBeenCalledTimes(1); + }); + }); + }); }); diff --git a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts index 33daa30a13f0..b605dd6ea5f3 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo-singleRun.test.ts @@ -2,6 +2,7 @@ import glob from 'glob'; import * as path from 'path'; import { getCanonicalFileName } from '../../src/create-program/shared'; +import { createProgramFromConfigFile as createProgramFromConfigFileOriginal } from '../../src/create-program/useProvidedPrograms'; import { clearParseAndGenerateServicesCalls, clearProgramCache, @@ -69,9 +70,9 @@ jest.mock('../../src/create-program/getWatchProgramsForProjects', () => { }; }); -const { - createProgramFromConfigFile, -} = require('../../src/create-program/useProvidedPrograms'); +const createProgramFromConfigFile = jest.mocked( + createProgramFromConfigFileOriginal, +); const FIXTURES_DIR = './tests/fixtures/semanticInfo'; const testFiles = glob.sync(`**/*.src.ts`, { @@ -97,7 +98,7 @@ describe('semanticInfo - singleRun', () => { // ensure caches are clean for each test clearProgramCache(); // ensure invocations of mock are clean for each test - (createProgramFromConfigFile as jest.Mock).mockClear(); + createProgramFromConfigFile.mockClear(); // Do not track invocations per file across tests clearParseAndGenerateServicesCalls(); }); @@ -232,7 +233,7 @@ describe('semanticInfo - singleRun', () => { const optionsWithReversedTsconfigs = { ...options, // Now the matching tsconfig comes first - project: options.project.reverse(), + project: [...options.project].reverse(), }; const resultProgram = parseAndGenerateServices( @@ -248,7 +249,7 @@ describe('semanticInfo - singleRun', () => { expect(createProgramFromConfigFile).toHaveBeenNthCalledWith( 1, - resolvedProject(tsconfigs[0]), + resolvedProject(tsconfigs[1]), ); // Restore process data diff --git a/packages/typescript-estree/tools/test-utils.ts b/packages/typescript-estree/tools/test-utils.ts index 6f40b597ca73..aef5601ebe2f 100644 --- a/packages/typescript-estree/tools/test-utils.ts +++ b/packages/typescript-estree/tools/test-utils.ts @@ -92,7 +92,7 @@ type UnknownObject = Record; function isObjectLike(value: unknown | null): value is UnknownObject { return ( - typeof value === 'object' && !(value instanceof RegExp) && value !== null + typeof value === 'object' && !(value instanceof RegExp) && value != null ); } diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index ec49364f3e77..f15c36d7d73f 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + + +### Features + +* **eslint-plugin:** [no-import-type-side-effects] add rule to warn against runtime side effects with `verbatimModuleSyntax` ([#6394](https://github.com/typescript-eslint/typescript-eslint/issues/6394)) ([b14d3be](https://github.com/typescript-eslint/typescript-eslint/commit/b14d3be0f305d71e0adfc9381e9de993898b2b43)) + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/utils diff --git a/packages/utils/package.json b/packages/utils/package.json index 13e83257c675..bc80a009477f 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "5.50.0", + "version": "5.51.0", "description": "Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -41,9 +41,9 @@ "dependencies": { "@types/json-schema": "^7.0.9", "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.50.0", - "@typescript-eslint/types": "5.50.0", - "@typescript-eslint/typescript-estree": "5.50.0", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", "eslint-scope": "^5.1.1", "eslint-utils": "^3.0.0", "semver": "^7.3.7" @@ -52,7 +52,7 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "devDependencies": { - "@typescript-eslint/parser": "5.50.0", + "@typescript-eslint/parser": "5.51.0", "typescript": "*" }, "funding": { diff --git a/packages/utils/src/ast-utils/predicates.ts b/packages/utils/src/ast-utils/predicates.ts index 6a65cab7625b..36e08ee5c42f 100644 --- a/packages/utils/src/ast-utils/predicates.ts +++ b/packages/utils/src/ast-utils/predicates.ts @@ -139,6 +139,20 @@ const isAwaitKeyword = isTokenOfTypeWithConditions(AST_TOKEN_TYPES.Identifier, { value: 'await', }); +/** + * Checks if a possible token is the `type` keyword. + */ +const isTypeKeyword = isTokenOfTypeWithConditions(AST_TOKEN_TYPES.Identifier, { + value: 'type', +}); + +/** + * Checks if a possible token is the `import` keyword. + */ +const isImportKeyword = isTokenOfTypeWithConditions(AST_TOKEN_TYPES.Keyword, { + value: 'import', +}); + const isLoop = isNodeOfTypes([ AST_NODE_TYPES.DoWhileStatement, AST_NODE_TYPES.ForStatement, @@ -156,6 +170,7 @@ export { isFunctionOrFunctionType, isFunctionType, isIdentifier, + isImportKeyword, isLoop, isLogicalOrOperator, isNonNullAssertionPunctuator, @@ -167,5 +182,6 @@ export { isTSConstructorType, isTSFunctionType, isTypeAssertion, + isTypeKeyword, isVariableDeclarator, }; diff --git a/packages/utils/src/eslint-utils/applyDefault.ts b/packages/utils/src/eslint-utils/applyDefault.ts index 85cd3b3653be..f16ae798715f 100644 --- a/packages/utils/src/eslint-utils/applyDefault.ts +++ b/packages/utils/src/eslint-utils/applyDefault.ts @@ -16,7 +16,7 @@ function applyDefault( JSON.stringify(defaultOptions), ) as AsMutable; - if (userOptions === null || userOptions === undefined) { + if (userOptions == null) { return options; } diff --git a/packages/utils/src/eslint-utils/nullThrows.ts b/packages/utils/src/eslint-utils/nullThrows.ts index df644c2befb0..1a79b2e09d43 100644 --- a/packages/utils/src/eslint-utils/nullThrows.ts +++ b/packages/utils/src/eslint-utils/nullThrows.ts @@ -18,7 +18,7 @@ function nullThrows(value: T | null | undefined, message: string): T { // so ignore it in coverage metrics. /* istanbul ignore if */ - if (value === null || value === undefined) { + if (value == null) { throw new Error(`Non-null Assertion Failed: ${message}`); } diff --git a/packages/utils/src/ts-eslint/SourceCode.ts b/packages/utils/src/ts-eslint/SourceCode.ts index 447c9debedbe..a44cdee3676b 100644 --- a/packages/utils/src/ts-eslint/SourceCode.ts +++ b/packages/utils/src/ts-eslint/SourceCode.ts @@ -389,10 +389,25 @@ namespace SourceCode { } export type FilterPredicate = (token: TSESTree.Token) => boolean; + export type GetFilterPredicate = + // https://github.com/prettier/prettier/issues/14275 + // prettier-ignore + TFilter extends (( + token: TSESTree.Token, + ) => token is infer U extends TSESTree.Token) + ? U + : TDefault; + export type GetFilterPredicateFromOptions = + TOptions extends { filter?: FilterPredicate } + ? GetFilterPredicate + : GetFilterPredicate; export type ReturnTypeFromOptions = T extends { includeComments: true } - ? TSESTree.Token - : Exclude; + ? GetFilterPredicateFromOptions + : GetFilterPredicateFromOptions< + T, + Exclude + >; export type CursorWithSkipOptions = | number diff --git a/packages/utils/tests/eslint-utils/nullThrows.test.ts b/packages/utils/tests/eslint-utils/nullThrows.test.ts new file mode 100644 index 000000000000..7997fecaa6df --- /dev/null +++ b/packages/utils/tests/eslint-utils/nullThrows.test.ts @@ -0,0 +1,31 @@ +import { nullThrows, NullThrowsReasons } from '../../src/eslint-utils'; + +describe('nullThrows', () => { + it('returns a falsy value when it exists', () => { + const value = 0; + + const actual = nullThrows(value, NullThrowsReasons.MissingParent); + + expect(actual).toBe(value); + }); + + it('returns a truthy value when it exists', () => { + const value = { abc: 'def' }; + + const actual = nullThrows(value, NullThrowsReasons.MissingParent); + + expect(actual).toBe(value); + }); + + it('throws an error when the value is null', () => { + expect(() => nullThrows(null, NullThrowsReasons.MissingParent)).toThrow( + NullThrowsReasons.MissingParent, + ); + }); + + it('throws an error when the value is undefined', () => { + expect(() => + nullThrows(undefined, NullThrowsReasons.MissingParent), + ).toThrow(NullThrowsReasons.MissingParent); + }); +}); diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index a7c260ce870f..3fa63a830424 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/visitor-keys + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/visitor-keys diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 14527dc3f80c..b984c406ee51 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "5.50.0", + "version": "5.51.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "keywords": [ "eslint", @@ -39,7 +39,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "5.50.0", + "@typescript-eslint/types": "5.51.0", "eslint-visitor-keys": "^3.3.0" }, "devDependencies": { diff --git a/packages/website-eslint/CHANGELOG.md b/packages/website-eslint/CHANGELOG.md index ea36b42047a0..33686f6b038c 100644 --- a/packages/website-eslint/CHANGELOG.md +++ b/packages/website-eslint/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package @typescript-eslint/website-eslint + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package @typescript-eslint/website-eslint diff --git a/packages/website-eslint/package.json b/packages/website-eslint/package.json index 6bb1f6d3f902..df5766f2ebe3 100644 --- a/packages/website-eslint/package.json +++ b/packages/website-eslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/website-eslint", - "version": "5.50.0", + "version": "5.51.0", "private": true, "description": "ESLint which works in browsers.", "engines": { @@ -16,19 +16,19 @@ "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore" }, "dependencies": { - "@typescript-eslint/types": "5.50.0", - "@typescript-eslint/utils": "5.50.0" + "@typescript-eslint/types": "5.51.0", + "@typescript-eslint/utils": "5.51.0" }, "devDependencies": { "@rollup/plugin-commonjs": "^23.0.0", "@rollup/plugin-json": "^5.0.0", "@rollup/plugin-node-resolve": "^15.0.0", "@rollup/pluginutils": "^5.0.0", - "@typescript-eslint/eslint-plugin": "5.50.0", - "@typescript-eslint/parser": "5.50.0", - "@typescript-eslint/scope-manager": "5.50.0", - "@typescript-eslint/typescript-estree": "5.50.0", - "@typescript-eslint/visitor-keys": "5.50.0", + "@typescript-eslint/eslint-plugin": "5.51.0", + "@typescript-eslint/parser": "5.51.0", + "@typescript-eslint/scope-manager": "5.51.0", + "@typescript-eslint/typescript-estree": "5.51.0", + "@typescript-eslint/visitor-keys": "5.51.0", "eslint": "*", "rollup": "^2.75.4", "rollup-plugin-terser": "^7.0.2", diff --git a/packages/website/CHANGELOG.md b/packages/website/CHANGELOG.md index 547367d081fd..2a28e8cdda0b 100644 --- a/packages/website/CHANGELOG.md +++ b/packages/website/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [5.51.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.50.0...v5.51.0) (2023-02-06) + +**Note:** Version bump only for package website + + + + + # [5.50.0](https://github.com/typescript-eslint/typescript-eslint/compare/v5.49.0...v5.50.0) (2023-01-31) **Note:** Version bump only for package website diff --git a/packages/website/data/sponsors.json b/packages/website/data/sponsors.json index 0ad6496d8453..f7c3f749e9dc 100644 --- a/packages/website/data/sponsors.json +++ b/packages/website/data/sponsors.json @@ -10,37 +10,44 @@ "id": "Nx (by Nrwl)", "image": "https://images.opencollective.com/nx/0efbe42/logo.png", "name": "Nx (by Nrwl)", - "totalDonations": 600000, + "totalDonations": 625000, "website": "https://nx.dev" }, { "id": "ESLint", "image": "https://images.opencollective.com/eslint/96b09dc/logo.png", "name": "ESLint", - "totalDonations": 245000, + "totalDonations": 260000, "website": "https://eslint.org/" }, { "id": "Hugging Face", "image": "https://images.opencollective.com/huggingface/5c934ee/logo.png", "name": "Hugging Face", - "totalDonations": 180000, + "totalDonations": 200000, "website": "https://huggingface.co" }, { "id": "Airbnb", "image": "https://images.opencollective.com/airbnb/d327d66/logo.png", "name": "Airbnb", - "totalDonations": 150800, + "totalDonations": 155800, "website": "https://www.airbnb.com/" }, { "id": "GitBook", "image": "https://images.opencollective.com/gitbook/d35a8e7/logo.png", "name": "GitBook", - "totalDonations": 130000, + "totalDonations": 140000, "website": "https://www.gitbook.com" }, + { + "id": "Codecademy", + "image": "https://images.opencollective.com/codecademy/d56a48d/logo.png", + "name": "Codecademy", + "totalDonations": 130000, + "website": "https://codecademy.com" + }, { "id": "n8n.io - n8n GmbH", "image": "https://images.opencollective.com/n8n/dca2f0c/logo.png", @@ -55,13 +62,6 @@ "totalDonations": 120000, "website": "https://blog.coinbase.com/engineering-and-security/home" }, - { - "id": "Codecademy", - "image": "https://images.opencollective.com/codecademy/d56a48d/logo.png", - "name": "Codecademy", - "totalDonations": 120000, - "website": "https://codecademy.com" - }, { "id": "Sentry", "image": "https://images.opencollective.com/sentry/9620d33/logo.png", @@ -80,9 +80,16 @@ "id": "Sourcegraph", "image": "https://images.opencollective.com/sourcegraph/67e40ff/logo.png", "name": "Sourcegraph", - "totalDonations": 70000, + "totalDonations": 80000, "website": "https://about.sourcegraph.com" }, + { + "id": "Codiga", + "image": "https://images.opencollective.com/codiga/1065f9f/logo.png", + "name": "Codiga", + "totalDonations": 60000, + "website": "https://www.codiga.io" + }, { "id": "Future Processing", "image": "https://images.opencollective.com/future-processing/1410d26/logo.png", @@ -90,13 +97,6 @@ "totalDonations": 54000, "website": "https://www.future-processing.com/" }, - { - "id": "Codiga", - "image": "https://images.opencollective.com/codiga/1065f9f/logo.png", - "name": "Codiga", - "totalDonations": 50000, - "website": "https://www.codiga.io" - }, { "id": "Whitebox", "image": "https://images.opencollective.com/whiteboxinc/ef0d11d/logo.png", @@ -108,7 +108,7 @@ "id": "STORIS", "image": "https://images.opencollective.com/storis/dfb0e13/logo.png", "name": "STORIS", - "totalDonations": 30000, + "totalDonations": 31500, "website": "https://www.storis.com/" }, { @@ -118,6 +118,13 @@ "totalDonations": 30000, "website": "https://www.monito.com" }, + { + "id": "DeepSource", + "image": "https://images.opencollective.com/deepsource/0f18cea/logo.png", + "name": "DeepSource", + "totalDonations": 30000, + "website": "https://deepsource.io/" + }, { "id": "revo.js", "image": "https://images.opencollective.com/revojsro/82623a7/logo.png", @@ -133,17 +140,17 @@ "website": "https://twitter.com/nevir" }, { - "id": "DeepSource", - "image": "https://images.opencollective.com/deepsource/0f18cea/logo.png", - "name": "DeepSource", + "id": "tRPC", + "image": "https://images.opencollective.com/trpc/82704a8/logo.png", + "name": "tRPC", "totalDonations": 20000, - "website": "https://deepsource.io/" + "website": "https://trpc.io" }, { "id": "David Johnston", "image": "https://images.opencollective.com/blacksheepcode/976d69a/avatar.png", "name": "David Johnston", - "totalDonations": 16000, + "totalDonations": 16500, "website": "https://blacksheepcode.com" }, { @@ -164,14 +171,14 @@ "id": "Evil Martians", "image": "https://images.opencollective.com/evilmartians/707ab4d/logo.png", "name": "Evil Martians", - "totalDonations": 11000, + "totalDonations": 11500, "website": "https://evilmartians.com/" }, { "id": "Balsa", "image": "https://images.opencollective.com/balsa/77de498/logo.png", "name": "Balsa", - "totalDonations": 11000, + "totalDonations": 11500, "website": "https://balsa.com" }, { @@ -194,12 +201,5 @@ "name": "Laserhub", "totalDonations": 10000, "website": "https://laserhub.com/" - }, - { - "id": "tRPC", - "image": "https://images.opencollective.com/trpc/82704a8/logo.png", - "name": "tRPC", - "totalDonations": 10000, - "website": "https://trpc.io" } ] diff --git a/packages/website/package.json b/packages/website/package.json index 8f4ee2b74aa0..30e2fdf8fba9 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "5.50.0", + "version": "5.51.0", "private": true, "scripts": { "build": "docusaurus build", @@ -21,8 +21,8 @@ "@docusaurus/remark-plugin-npm2yarn": "~2.2.0", "@docusaurus/theme-common": "~2.2.0", "@mdx-js/react": "1.6.22", - "@typescript-eslint/parser": "5.50.0", - "@typescript-eslint/website-eslint": "5.50.0", + "@typescript-eslint/parser": "5.51.0", + "@typescript-eslint/website-eslint": "5.51.0", "clsx": "^1.1.1", "eslint": "*", "json-schema": "^0.4.0", @@ -48,7 +48,7 @@ "@types/react": "^18.0.9", "@types/react-helmet": "^6.1.5", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "5.50.0", + "@typescript-eslint/eslint-plugin": "5.51.0", "copy-webpack-plugin": "^11.0.0", "eslint-plugin-jsx-a11y": "^6.5.1", "eslint-plugin-react": "^7.29.4", diff --git a/packages/website/src/components/ast/serializer/serializer.ts b/packages/website/src/components/ast/serializer/serializer.ts index 74a94de9a479..f6c75be3302b 100644 --- a/packages/website/src/components/ast/serializer/serializer.ts +++ b/packages/website/src/components/ast/serializer/serializer.ts @@ -26,7 +26,7 @@ function getSimpleModel(data: unknown): ASTViewerModelSimple { value: String(data), type: 'regexp', }; - } else if (typeof data === 'undefined' || data === null) { + } else if (data == null) { return { value: String(data), type: 'undefined', diff --git a/tests/jest-resolver.js b/tests/jest-resolver.js deleted file mode 100644 index 0636e2feee7e..000000000000 --- a/tests/jest-resolver.js +++ /dev/null @@ -1,20 +0,0 @@ -/* @ts-check */ - -// temporary workaround - https://github.com/facebook/jest/issues/9771#issuecomment-871585234 -const resolver = require('enhanced-resolve').create.sync({ - conditionNames: ['require', 'node', 'default'], - extensions: ['.js', '.json', '.node', '.ts', '.tsx'], -}); - -/** - * @param request {unknown} - * @param options {{ defaultResolver(...args: unknown[]): unknown, basedir: unknown }} - * @returns {unknown} - */ -module.exports = function (request, options) { - // list global module that must be resolved by defaultResolver here - if (['fs', 'http', 'path'].includes(request)) { - return options.defaultResolver(request, options); - } - return resolver(options.basedir, request); -}; diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json index 70fafc555473..36b912142b9a 100644 --- a/tsconfig.eslint.json +++ b/tsconfig.eslint.json @@ -10,7 +10,6 @@ ".eslintrc.js", "jest.config.base.js", "jest.config.js", - "jest.preset.js", - "tests/jest-resolver.js" + "jest.preset.js" ] } diff --git a/yarn.lock b/yarn.lock index bbe5aba913b6..228df7635b85 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3246,6 +3246,13 @@ read-package-json-fast "^2.0.3" which "^2.0.2" +"@nrwl/cli@15.5.3": + version "15.5.3" + resolved "https://registry.yarnpkg.com/@nrwl/cli/-/cli-15.5.3.tgz#13277e5a0e8ba713850bcf13fa76717ea747a2bb" + integrity sha512-NWf9CWswvdYM6YzXuweaZPAZ2erMtQrrHZdgFbUGeojZBZ+b4TCGzLWNodZj4yQOa/eTwlyPMYO2LEw9CoapDQ== + dependencies: + nx "15.5.3" + "@nrwl/cli@15.6.3": version "15.6.3" resolved "https://registry.npmjs.org/@nrwl/cli/-/cli-15.6.3.tgz#999531d6efb30afc39373bdcbd7e78254a3a3fd3" @@ -3317,6 +3324,13 @@ tar "6.1.11" yargs-parser ">=21.0.1" +"@nrwl/tao@15.5.3": + version "15.5.3" + resolved "https://registry.yarnpkg.com/@nrwl/tao/-/tao-15.5.3.tgz#08c05715d2ecb108ed8b2c5381b9017cf1448b4a" + integrity sha512-vgPLIW9IoBfQ4IkHRT5RC4LqNwFBK5jmHYmFIRgbIeFRudFBbnpmOaKRME0OwN7qJ6964PVVbzahAPvYVD02xw== + dependencies: + nx "15.5.3" + "@nrwl/tao@15.6.3": version "15.6.3" resolved "https://registry.npmjs.org/@nrwl/tao/-/tao-15.6.3.tgz#b24e11345375dea96bc386c60b9b1102a7584932" @@ -6714,7 +6728,7 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -enhanced-resolve@^5.10.0, enhanced-resolve@^5.9.3: +enhanced-resolve@^5.10.0: version "5.10.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6" integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ== @@ -10780,13 +10794,13 @@ nth-check@^2.0.0, nth-check@^2.0.1: dependencies: boolbase "^1.0.0" -nx@15.6.3: - version "15.6.3" - resolved "https://registry.npmjs.org/nx/-/nx-15.6.3.tgz#900087bce38c6e5975660c23ebd41ead1bf54f98" - integrity sha512-3t0A0GPLNen1yPAyE+VGZ3nkAzZYb5nfXtAcx8SHBlKq4u42yBY3khBmP1y4Og3jhIwFIj7J7Npeh8ZKrthmYQ== +nx@15.5.3, "nx@>=15.4.2 < 16": + version "15.5.3" + resolved "https://registry.npmjs.org/nx/-/nx-15.5.3.tgz#bf6252e7d9e17121dd82dec4f6fce319b9e005fa" + integrity sha512-PHB8VbiBLP108xb+yR8IGEsYWr7OcmDDOjHL+73oP4lVjyPgT8wdTMe6tI5LdBgv+KZ+0kiThK3ckvcPsfgvLQ== dependencies: - "@nrwl/cli" "15.6.3" - "@nrwl/tao" "15.6.3" + "@nrwl/cli" "15.5.3" + "@nrwl/tao" "15.5.3" "@parcel/watcher" "2.0.4" "@yarnpkg/lockfile" "^1.1.0" "@yarnpkg/parsers" "^3.0.0-rc.18" @@ -10821,13 +10835,13 @@ nx@15.6.3: yargs "^17.6.2" yargs-parser "21.1.1" -"nx@>=15.4.2 < 16": - version "15.5.3" - resolved "https://registry.npmjs.org/nx/-/nx-15.5.3.tgz#bf6252e7d9e17121dd82dec4f6fce319b9e005fa" - integrity sha512-PHB8VbiBLP108xb+yR8IGEsYWr7OcmDDOjHL+73oP4lVjyPgT8wdTMe6tI5LdBgv+KZ+0kiThK3ckvcPsfgvLQ== +nx@15.6.3: + version "15.6.3" + resolved "https://registry.npmjs.org/nx/-/nx-15.6.3.tgz#900087bce38c6e5975660c23ebd41ead1bf54f98" + integrity sha512-3t0A0GPLNen1yPAyE+VGZ3nkAzZYb5nfXtAcx8SHBlKq4u42yBY3khBmP1y4Og3jhIwFIj7J7Npeh8ZKrthmYQ== dependencies: - "@nrwl/cli" "15.5.3" - "@nrwl/tao" "15.5.3" + "@nrwl/cli" "15.6.3" + "@nrwl/tao" "15.6.3" "@parcel/watcher" "2.0.4" "@yarnpkg/lockfile" "^1.1.0" "@yarnpkg/parsers" "^3.0.0-rc.18"