From 385bedb3cdb63024c6e433a5d208155b38d142cf Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Wed, 18 Sep 2024 00:14:08 +0000 Subject: [PATCH 01/16] fix(utils): add missing entries to the RuleListener selectors list (#9992) * fix(utils): add missing entries to the RuleListener selectors list * fix knip err --- knip.ts | 3 ++ packages/utils/src/ts-eslint/Rule.ts | 11 +++++- .../utils/tests/ts-eslint/Rule.type-test.ts | 37 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 packages/utils/tests/ts-eslint/Rule.type-test.ts diff --git a/knip.ts b/knip.ts index e1b4d2ced348..51e66b91a598 100644 --- a/knip.ts +++ b/knip.ts @@ -65,6 +65,9 @@ export default { entry: ['src/use-at-your-own-risk.ts'], ignore: ['tests/fixtures/**'], }, + 'packages/utils': { + ignore: ['tests/**/*.type-test.ts'], + }, 'packages/website': { entry: [ 'docusaurus.config.mts', diff --git a/packages/utils/src/ts-eslint/Rule.ts b/packages/utils/src/ts-eslint/Rule.ts index 0d409a83e3f5..5fdd4c6776ef 100644 --- a/packages/utils/src/ts-eslint/Rule.ts +++ b/packages/utils/src/ts-eslint/Rule.ts @@ -423,7 +423,6 @@ interface RuleListenerBaseSelectors { AssignmentExpression?: RuleFunction; AssignmentPattern?: RuleFunction; AwaitExpression?: RuleFunction; - BigIntLiteral?: RuleFunction; BinaryExpression?: RuleFunction; BlockStatement?: RuleFunction; BreakStatement?: RuleFunction; @@ -451,6 +450,7 @@ interface RuleListenerBaseSelectors { FunctionExpression?: RuleFunction; Identifier?: RuleFunction; IfStatement?: RuleFunction; + ImportAttribute?: RuleFunction; ImportDeclaration?: RuleFunction; ImportDefaultSpecifier?: RuleFunction; ImportExpression?: RuleFunction; @@ -465,6 +465,7 @@ interface RuleListenerBaseSelectors { JSXFragment?: RuleFunction; JSXIdentifier?: RuleFunction; JSXMemberExpression?: RuleFunction; + JSXNamespacedName?: RuleFunction; JSXOpeningElement?: RuleFunction; JSXOpeningFragment?: RuleFunction; JSXSpreadAttribute?: RuleFunction; @@ -479,6 +480,7 @@ interface RuleListenerBaseSelectors { NewExpression?: RuleFunction; ObjectExpression?: RuleFunction; ObjectPattern?: RuleFunction; + PrivateIdentifier?: RuleFunction; Program?: RuleFunction; Property?: RuleFunction; PropertyDefinition?: RuleFunction; @@ -486,6 +488,7 @@ interface RuleListenerBaseSelectors { ReturnStatement?: RuleFunction; SequenceExpression?: RuleFunction; SpreadElement?: RuleFunction; + StaticBlock?: RuleFunction; Super?: RuleFunction; SwitchCase?: RuleFunction; SwitchStatement?: RuleFunction; @@ -495,6 +498,7 @@ interface RuleListenerBaseSelectors { ThisExpression?: RuleFunction; ThrowStatement?: RuleFunction; TryStatement?: RuleFunction; + TSAbstractAccessorProperty?: RuleFunction; TSAbstractKeyword?: RuleFunction; TSAbstractMethodDefinition?: RuleFunction; TSAbstractPropertyDefinition?: RuleFunction; @@ -512,6 +516,7 @@ interface RuleListenerBaseSelectors { TSDeclareFunction?: RuleFunction; TSDeclareKeyword?: RuleFunction; TSEmptyBodyFunctionExpression?: RuleFunction; + TSEnumBody?: RuleFunction; TSEnumDeclaration?: RuleFunction; TSEnumMember?: RuleFunction; TSExportAssignment?: RuleFunction; @@ -523,15 +528,18 @@ interface RuleListenerBaseSelectors { TSIndexedAccessType?: RuleFunction; TSIndexSignature?: RuleFunction; TSInferType?: RuleFunction; + TSInstantiationExpression?: RuleFunction; TSInterfaceBody?: RuleFunction; TSInterfaceDeclaration?: RuleFunction; TSInterfaceHeritage?: RuleFunction; TSIntersectionType?: RuleFunction; + TSIntrinsicKeyword?: RuleFunction; TSLiteralType?: RuleFunction; TSMappedType?: RuleFunction; TSMethodSignature?: RuleFunction; TSModuleBlock?: RuleFunction; TSModuleDeclaration?: RuleFunction; + TSNamedTupleMember?: RuleFunction; TSNamespaceExportDeclaration?: RuleFunction; TSNeverKeyword?: RuleFunction; TSNonNullExpression?: RuleFunction; @@ -551,6 +559,7 @@ interface RuleListenerBaseSelectors { TSStaticKeyword?: RuleFunction; TSStringKeyword?: RuleFunction; TSSymbolKeyword?: RuleFunction; + TSTemplateLiteralType?: RuleFunction; TSThisType?: RuleFunction; TSTupleType?: RuleFunction; TSTypeAliasDeclaration?: RuleFunction; diff --git a/packages/utils/tests/ts-eslint/Rule.type-test.ts b/packages/utils/tests/ts-eslint/Rule.type-test.ts new file mode 100644 index 000000000000..5657208f80ff --- /dev/null +++ b/packages/utils/tests/ts-eslint/Rule.type-test.ts @@ -0,0 +1,37 @@ +import type { TSESTree } from '@typescript-eslint/types'; + +import type { RuleListener } from '../../src/ts-eslint'; + +type RuleListenerKeysWithoutIndexSignature = { + [K in keyof RuleListener as string extends K ? never : K]: K; +}; + +type RuleListenerSelectors = NonNullable< + RuleListenerKeysWithoutIndexSignature[keyof RuleListenerKeysWithoutIndexSignature] +>; + +type AllSelectors = + | `${TSESTree.AST_NODE_TYPES}` + | `${TSESTree.AST_NODE_TYPES}:exit`; + +type ExpectNever = T; + +type SelectorsWithWrongNodeType = { + [K in TSESTree.AST_NODE_TYPES]: Parameters< + NonNullable + >[0]['type'] extends K + ? K extends Parameters>[0]['type'] + ? never + : K + : K; +}[TSESTree.AST_NODE_TYPES]; +type _test_rule_listener_selectors_have_correct_node_types = + ExpectNever; + +type ExtraSelectors = Exclude; +type _test_rule_listener_does_not_define_extra_selectors = + ExpectNever; + +type MissingSelectors = Exclude; +type _test_rule_listener_has_selectors_for_all_node_types = + ExpectNever; From 7865a55c95fd4597c1e6a10292e45db5412e79cd Mon Sep 17 00:00:00 2001 From: Sol Lee <82362278+saul-atomrigs@users.noreply.github.com> Date: Thu, 19 Sep 2024 20:55:05 +0900 Subject: [PATCH 02/16] docs: fix typo for `only-throw-error` rule (#10018) --- packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md index 262cfbb3850c..eb5fccc1a410 100644 --- a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md +++ b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md @@ -85,7 +85,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`no-shadowed-variable`] | 🌟 | [`no-shadow`][no-shadow] | | [`no-sparse-arrays`] | 🌟 | [`no-sparse-arrays`][no-sparse-arrays] | | [`no-string-literal`] | 🌟 | [`dot-notation`][dot-notation] | -| [`no-string-throw`] | ✅ | [`@typescript-eslint/only-throw-literal`] | +| [`no-string-throw`] | ✅ | [`@typescript-eslint/only-throw-error`] | | [`no-submodule-imports`] | 🌓 | [`import/no-internal-modules`] (slightly different) | | [`no-switch-case-fall-through`] | 🌟 | [`no-fallthrough`][no-fallthrough] | | [`no-tautology-expression`] | 🛑 | N/A | @@ -622,7 +622,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/no-unnecessary-boolean-literal-compare`]: https://typescript-eslint.io/rules/no-unnecessary-boolean-literal-compare [`@typescript-eslint/no-misused-new`]: https://typescript-eslint.io/rules/no-misused-new [`@typescript-eslint/no-this-alias`]: https://typescript-eslint.io/rules/no-this-alias -[`@typescript-eslint/only-throw-literal`]: https://typescript-eslint.io/rules/only-throw-literal +[`@typescript-eslint/only-throw-error`]: https://typescript-eslint.io/rules/only-throw-error [`@typescript-eslint/no-extraneous-class`]: https://typescript-eslint.io/rules/no-extraneous-class [`@typescript-eslint/no-unused-vars`]: https://typescript-eslint.io/rules/no-unused-vars [`@typescript-eslint/no-use-before-define`]: https://typescript-eslint.io/rules/no-use-before-define From d6e0f6e7da7b81d1e4b70d3342651c0e7e29c000 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 19 Sep 2024 12:57:48 -0500 Subject: [PATCH 03/16] chore: run prettier to fix CI failure on `main` (#10020) prettier --- packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md index eb5fccc1a410..eab5e19d3586 100644 --- a/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md +++ b/packages/eslint-plugin/TSLINT_RULE_ALTERNATIVES.md @@ -85,7 +85,7 @@ It lists all TSLint rules along side rules from the ESLint ecosystem that are th | [`no-shadowed-variable`] | 🌟 | [`no-shadow`][no-shadow] | | [`no-sparse-arrays`] | 🌟 | [`no-sparse-arrays`][no-sparse-arrays] | | [`no-string-literal`] | 🌟 | [`dot-notation`][dot-notation] | -| [`no-string-throw`] | ✅ | [`@typescript-eslint/only-throw-error`] | +| [`no-string-throw`] | ✅ | [`@typescript-eslint/only-throw-error`] | | [`no-submodule-imports`] | 🌓 | [`import/no-internal-modules`] (slightly different) | | [`no-switch-case-fall-through`] | 🌟 | [`no-fallthrough`][no-fallthrough] | | [`no-tautology-expression`] | 🛑 | N/A | From c249c2f5a24cc246c38bfcc69be2bafe76a0ea79 Mon Sep 17 00:00:00 2001 From: Brian Donovan <1938+eventualbuddha@users.noreply.github.com> Date: Thu, 19 Sep 2024 16:38:26 -0700 Subject: [PATCH 04/16] fix(types): add `NewExpression` as a parent of `SpreadElement` (#10024) `new` expressions may contain a spread, e.g. `new Array(...[])`. --- packages/types/src/ts-estree.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/types/src/ts-estree.ts b/packages/types/src/ts-estree.ts index 15d5204940ac..9656ea011dc7 100644 --- a/packages/types/src/ts-estree.ts +++ b/packages/types/src/ts-estree.ts @@ -138,6 +138,7 @@ declare module './generated/ast-spec' { parent: | TSESTree.ArrayExpression | TSESTree.CallExpression + | TSESTree.NewExpression | TSESTree.ObjectExpression; } From dc5b5873c9443c9f567df086d769801617f0461e Mon Sep 17 00:00:00 2001 From: "Samuel T." Date: Thu, 19 Sep 2024 22:16:39 -0400 Subject: [PATCH 05/16] docs: misnamed `no-deprecation`/`no-deprecated` in CHANGELOG.md (#10021) Misnamed `no-deprecation`/`no-deprecated` in CHANGELOG.md Same in the release description for https://github.com/typescript-eslint/typescript-eslint/releases/tag/v8.3.0 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f6bbc3751139..ce3126e8adb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -71,7 +71,7 @@ You can read about our [versioning strategy](https://main--typescript-eslint.net ### 🚀 Features -- **eslint-plugin:** [no-deprecation] add rule ([#9783](https://github.com/typescript-eslint/typescript-eslint/pull/9783)) +- **eslint-plugin:** [no-deprecated] add rule ([#9783](https://github.com/typescript-eslint/typescript-eslint/pull/9783)) - **typescript-estree:** replace `globby` w/ `fast-glob` ([#9518](https://github.com/typescript-eslint/typescript-eslint/pull/9518)) - **typescript-estree:** reload project service once when file config isn't found ([#9853](https://github.com/typescript-eslint/typescript-eslint/pull/9853)) From 4b5cf6eaffcc8933581eb42bca77ee6778a38ffb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:19:22 -0400 Subject: [PATCH 06/16] chore(deps): update dependency eslint-plugin-react to v7.36.1 (#10017) chore(deps): update dependency eslint-plugin-react to v7.36.0 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 91ed28cd5251..cf3a848cc305 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9898,8 +9898,8 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.34.1": - version: 7.35.0 - resolution: "eslint-plugin-react@npm:7.35.0" + version: 7.36.1 + resolution: "eslint-plugin-react@npm:7.36.1" dependencies: array-includes: ^3.1.8 array.prototype.findlast: ^1.2.5 @@ -9921,7 +9921,7 @@ __metadata: string.prototype.repeat: ^1.0.0 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - checksum: cd4d3c0567e947964643dda5fc80147e058d75f06bac47c3f086ff0cd6156286c669d98e685e3834997c4043f3922b90e6374b6c3658f22abd025dbd41acc23f + checksum: bf3be414f3d639200a7d91feeaa6beec3397feed93ab22eaecef44dda37ecbd01812ed1720c72a9861fb276d3543cea69a834a66f64de3d878796fef4f4bf129 languageName: node linkType: hard From 7b149b9a56aea0cb32e810987637bfd163614e0c Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Sat, 21 Sep 2024 14:49:47 -0600 Subject: [PATCH 07/16] docs: [strict-boolean-expressions] fix adominition (#10033) * docs: [strict-boolean-expressions] fix adominition * Update packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx --------- Co-authored-by: Joshua Chen --- .../docs/rules/strict-boolean-expressions.mdx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx index 9323c6351e5a..820d4d188d4e 100644 --- a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.mdx @@ -146,9 +146,11 @@ Set this to `true` at your own risk. :::danger Deprecated -> This option will be removed in the next major version of typescript-eslint. -> ::: -> If this is set to `false`, then the rule will error on every file whose `tsconfig.json` does _not_ have the `strictNullChecks` compiler option (or `strict`) set to `true`. +This option will be removed in the next major version of typescript-eslint. + +::: + +If this is set to `false`, then the rule will error on every file whose `tsconfig.json` does _not_ have the `strictNullChecks` compiler option (or `strict`) set to `true`. Without `strictNullChecks`, TypeScript essentially erases `undefined` and `null` from the types. This means when this rule inspects the types from a variable, **it will not be able to tell that the variable might be `null` or `undefined`**, which essentially makes this rule a lot less useful. From 1c183ab1f2cfbf1167301b33b460f2a4b2dafb36 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 22 Sep 2024 13:33:19 -1000 Subject: [PATCH 08/16] fix(eslint-plugin): properly coerce all types to string in `getStaticMemberAccessValue` (#10004) --- .../src/rules/class-literal-property-style.ts | 7 +- .../src/rules/class-methods-use-this.ts | 4 +- .../util/isArrayMethodCallWithPredicate.ts | 4 +- packages/eslint-plugin/src/util/misc.ts | 79 +++++++++++++++---- .../explicit-module-boundary-types.test.ts | 3 +- .../prefer-promise-reject-errors.test.ts | 9 +++ 6 files changed, 82 insertions(+), 24 deletions(-) diff --git a/packages/eslint-plugin/src/rules/class-literal-property-style.ts b/packages/eslint-plugin/src/rules/class-literal-property-style.ts index 28d4a5cc78ba..9f02142f4d6e 100644 --- a/packages/eslint-plugin/src/rules/class-literal-property-style.ts +++ b/packages/eslint-plugin/src/rules/class-literal-property-style.ts @@ -4,7 +4,6 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import { createRule, getStaticMemberAccessValue, - getStaticStringValue, isAssignee, isFunction, isStaticMemberAccessOfValue, @@ -25,7 +24,7 @@ interface NodeWithModifiers { interface PropertiesInfo { properties: TSESTree.PropertyDefinition[]; - excludeSet: Set; + excludeSet: Set; } const printNodeModifiers = ( @@ -132,9 +131,7 @@ export default createRule({ const { excludeSet } = propertiesInfoStack[propertiesInfoStack.length - 1]; - const name = - getStaticStringValue(node.property) ?? - context.sourceCode.getText(node.property); + const name = getStaticMemberAccessValue(node, context); if (name) { excludeSet.add(name); diff --git a/packages/eslint-plugin/src/rules/class-methods-use-this.ts b/packages/eslint-plugin/src/rules/class-methods-use-this.ts index 6e27a6229f12..4b369d7a51be 100644 --- a/packages/eslint-plugin/src/rules/class-methods-use-this.ts +++ b/packages/eslint-plugin/src/rules/class-methods-use-this.ts @@ -184,7 +184,9 @@ export default createRule({ node.key.type === AST_NODE_TYPES.PrivateIdentifier ? '#' : ''; const name = getStaticMemberAccessValue(node, context); - return !exceptMethods.has(hashIfNeeded + (name ?? '')); + return ( + typeof name !== 'string' || !exceptMethods.has(hashIfNeeded + name) + ); } /** diff --git a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts index 746e9003722c..39e46d9dda39 100644 --- a/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts +++ b/packages/eslint-plugin/src/util/isArrayMethodCallWithPredicate.ts @@ -9,7 +9,7 @@ import * as tsutils from 'ts-api-utils'; import { getStaticMemberAccessValue } from './misc'; -const ARRAY_PREDICATE_FUNCTIONS = new Set([ +const ARRAY_PREDICATE_FUNCTIONS = new Set([ 'filter', 'find', 'findIndex', @@ -30,7 +30,7 @@ export function isArrayMethodCallWithPredicate( const staticAccessValue = getStaticMemberAccessValue(node.callee, context); - if (!staticAccessValue || !ARRAY_PREDICATE_FUNCTIONS.has(staticAccessValue)) { + if (!ARRAY_PREDICATE_FUNCTIONS.has(staticAccessValue)) { return false; } diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index aeed9f96d039..41933dc551c2 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -239,36 +239,85 @@ type NodeWithKey = | TSESTree.PropertyDefinition | TSESTree.TSAbstractMethodDefinition | TSESTree.TSAbstractPropertyDefinition; + +/** + * Gets a member being accessed or declared if its value can be determined statically, and + * resolves it to the string or symbol value that will be used as the actual member + * access key at runtime. Otherwise, returns `undefined`. + * + * ```ts + * x.member // returns 'member' + * ^^^^^^^^ + * + * x?.member // returns 'member' (optional chaining is treated the same) + * ^^^^^^^^^ + * + * x['value'] // returns 'value' + * ^^^^^^^^^^ + * + * x[Math.random()] // returns undefined (not a static value) + * ^^^^^^^^^^^^^^^^ + * + * arr[0] // returns '0' (NOT 0) + * ^^^^^^ + * + * arr[0n] // returns '0' (NOT 0n) + * ^^^^^^^ + * + * const s = Symbol.for('symbolName') + * x[s] // returns `Symbol.for('symbolName')` (since it's a static/global symbol) + * ^^^^ + * + * const us = Symbol('symbolName') + * x[us] // returns undefined (since it's a unique symbol, so not statically analyzable) + * ^^^^^ + * + * var object = { + * 1234: '4567', // returns '1234' (NOT 1234) + * ^^^^^^^^^^^^ + * method() { } // returns 'method' + * ^^^^^^^^^^^^ + * } + * + * class WithMembers { + * foo: string // returns 'foo' + * ^^^^^^^^^^^ + * } + * ``` + */ function getStaticMemberAccessValue( node: NodeWithKey, { sourceCode }: RuleContext, -): string | undefined { +): string | symbol | undefined { const key = node.type === AST_NODE_TYPES.MemberExpression ? node.property : node.key; - if (!node.computed) { - return key.type === AST_NODE_TYPES.Literal - ? `${key.value}` - : (key as TSESTree.Identifier | TSESTree.PrivateIdentifier).name; + const { type } = key; + if ( + !node.computed && + (type === AST_NODE_TYPES.Identifier || + type === AST_NODE_TYPES.PrivateIdentifier) + ) { + return key.name; + } + const result = getStaticValue(key, sourceCode.getScope(node)); + if (!result) { + return undefined; } - const value = getStaticValue(key, sourceCode.getScope(node))?.value as - | string - | number - | null - | undefined; - return value == null ? undefined : `${value}`; + const { value } = result; + return typeof value === 'symbol' ? value : String(value); } /** * Answers whether the member expression looks like - * `x.memberName`, `x['memberName']`, - * or even `const mn = 'memberName'; x[mn]` (or optional variants thereof). + * `x.value`, `x['value']`, + * or even `const v = 'value'; x[v]` (or optional variants thereof). */ const isStaticMemberAccessOfValue = ( memberExpression: NodeWithKey, context: RuleContext, - ...values: string[] + ...values: (string | symbol)[] ): boolean => - (values as (string | undefined)[]).includes( + (values as (string | symbol | undefined)[]).includes( getStaticMemberAccessValue(memberExpression, context), ); diff --git a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts index eda213576ff8..3bff869ae8b1 100644 --- a/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-module-boundary-types.test.ts @@ -407,6 +407,7 @@ export class Test { 'method'() {} ['prop']() {} [\`prop\`]() {} + [null]() {} [\`\${v}\`](): void {} foo = () => { @@ -416,7 +417,7 @@ export class Test { `, options: [ { - allowedNames: ['prop', 'method', 'foo'], + allowedNames: ['prop', 'method', 'null', 'foo'], }, ], }, diff --git a/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts index d890886eff15..e78575881255 100644 --- a/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts @@ -266,6 +266,15 @@ ruleTester.run('prefer-promise-reject-errors', rule, { declare const foo: PromiseConstructor; foo.reject(new Error()); `, + 'console[Symbol.iterator]();', + ` + class A { + a = []; + [Symbol.iterator]() { + return this.a[Symbol.iterator](); + } + } + `, ], invalid: [ { From c5dc75566289dbcf8edede63ba624db00404e5ac Mon Sep 17 00:00:00 2001 From: Alexander Cerutti Date: Mon, 23 Sep 2024 08:30:40 +0200 Subject: [PATCH 09/16] docs: [no-floating-promises] clarify that void does not resolve promises (#9949) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Improved no-floating-promises documentation * Changed silencing phrase * Moved rules report order to prioritize handling * Improved ignoreVoid warning * Removed handled/unhandled markers and added a bit on the next tip * Reverted tip edit * Added a paragraph about promises handling and improved the warning in ignoreVoid * Moved handling Promises paragraph up * Reworked a bit Handling Promises rejections * Removed Handling Promises rejection * Added link to mdn in the tip * Removed ref to handling promises rejection (not existing anymore) * Changed monospace comment in warning to link Co-authored-by: Josh Goldberg ✨ * Typo fix Co-authored-by: Josh Goldberg ✨ --------- Co-authored-by: Josh Goldberg ✨ Co-authored-by: Kirk Waiblinger --- .../docs/rules/no-floating-promises.mdx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx index 00deb4edc979..355fe6aeb141 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -12,14 +12,13 @@ import TabItem from '@theme/TabItem'; A "floating" Promise is one that is created without any code set up to handle any errors it might throw. Floating Promises can cause several issues, such as improperly sequenced operations, ignored Promise rejections, and more. -This rule reports when a Promise is created and not properly handled. -Valid ways of handling a Promise-valued statement include: +This rule will report Promise-valued statements that are not treated in one of the following ways: -- `await`ing it -- `return`ing it -- `void`ing it - Calling its `.then()` with two arguments - Calling its `.catch()` with one argument +- `await`ing it +- `return`ing it +- [`void`ing it](#ignorevoid) This rule also reports when an Array containing Promises is created and not properly handled. The main way to resolve this is by using one of the Promise concurrency methods to create a single Promise, then handling that according to the procedure above. These methods include: @@ -29,8 +28,10 @@ This rule also reports when an Array containing Promises is created and not prop - `Promise.race()` :::tip -`no-floating-promises` only detects unhandled Promise _statements_. +`no-floating-promises` only detects apparently unhandled Promise _statements_. See [`no-misused-promises`](./no-misused-promises.mdx) for detecting code that provides Promises to _logical_ locations such as if statements. + +See [_Using promises (error handling) on MDN_](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises#error_handling) for a detailed writeup on Promise error-handling. ::: ## Examples @@ -134,6 +135,12 @@ await createMyThenable(); This option, which is `true` by default, allows you to stop the rule reporting promises consumed with the [`void` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void). This can be a good way to explicitly mark a promise as intentionally not awaited. +:::warning +Voiding a Promise doesn't handle it or change the runtime behavior. +The outcome is just ignored, like disabling the rule with an [ESLint disable comment](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1). +Such Promise rejections will still be unhandled. +::: + Examples of **correct** code for this rule with `{ ignoreVoid: true }`: ```ts option='{ "ignoreVoid": true }' showPlaygroundButton From b75d42be049b6d221cb20e1d91f441eef79f1787 Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:11:32 +0000 Subject: [PATCH 10/16] fix(eslint-plugin): [no-deprecated] report on imported deprecated variables (#9987) * fix(eslint-plugin): [no-deprecated] report on imported deprecated variables * i thought it would be a lot easier... * revert changes in class.ts fixture * explain why we don't check aliased symbol when working with signatures * ci failures * test: make tests pass with projectservice enabled --- .../eslint-plugin/src/rules/no-deprecated.ts | 155 ++-- .../tests/fixtures/deprecated.ts | 42 + .../tests/fixtures/tsconfig.json | 1 + .../tsconfig.moduleResolution-node16.json | 7 + .../tests/rules/no-deprecated.test.ts | 768 +++++++++++++++++- 5 files changed, 918 insertions(+), 55 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/deprecated.ts create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json diff --git a/packages/eslint-plugin/src/rules/no-deprecated.ts b/packages/eslint-plugin/src/rules/no-deprecated.ts index ddbe08eb0926..42eb522d3ca9 100644 --- a/packages/eslint-plugin/src/rules/no-deprecated.ts +++ b/packages/eslint-plugin/src/rules/no-deprecated.ts @@ -3,7 +3,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { createRule, getParserServices, nullThrows } from '../util'; type IdentifierLike = TSESTree.Identifier | TSESTree.JSXIdentifier; @@ -34,6 +34,42 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); + // Deprecated jsdoc tags can be added on some symbol alias, e.g. + // + // export { /** @deprecated */ foo } + // + // When we import foo, its symbol is an alias of the exported foo (the one + // with the deprecated tag), which is itself an alias of the original foo. + // Therefore, we carefully go through the chain of aliases and check each + // immediate alias for deprecated tags + function searchForDeprecationInAliasesChain( + symbol: ts.Symbol | undefined, + checkDeprecationsOfAliasedSymbol: boolean, + ): string | undefined { + if (!symbol || !tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) { + return checkDeprecationsOfAliasedSymbol + ? getJsDocDeprecation(symbol) + : undefined; + } + const targetSymbol = checker.getAliasedSymbol(symbol); + while (tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias)) { + const reason = getJsDocDeprecation(symbol); + if (reason !== undefined) { + return reason; + } + const immediateAliasedSymbol: ts.Symbol | undefined = + symbol.getDeclarations() && checker.getImmediateAliasedSymbol(symbol); + if (!immediateAliasedSymbol) { + break; + } + symbol = immediateAliasedSymbol; + if (checkDeprecationsOfAliasedSymbol && symbol === targetSymbol) { + return getJsDocDeprecation(symbol); + } + } + return undefined; + } + function isDeclaration(node: IdentifierLike): boolean { const { parent } = node; @@ -169,61 +205,82 @@ export default createRule({ const tsNode = services.esTreeNodeToTSNodeMap.get(node.parent); // If the node is a direct function call, we look for its signature. - const signature = checker.getResolvedSignature( - tsNode as ts.CallLikeExpression, + const signature = nullThrows( + checker.getResolvedSignature(tsNode as ts.CallLikeExpression), + 'Expected call like node to have signature', ); - const symbol = services.getSymbolAtLocation(node); - if (signature) { - const signatureDeprecation = getJsDocDeprecation(signature); - if (signatureDeprecation !== undefined) { - return signatureDeprecation; - } - // Properties with function-like types have "deprecated" jsdoc - // on their symbols, not on their signatures: - // - // interface Props { - // /** @deprecated */ - // property: () => 'foo' - // ^symbol^ ^signature^ - // } - const symbolDeclarationKind = symbol?.declarations?.[0].kind; - if ( - symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration && - symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration && - symbolDeclarationKind !== ts.SyntaxKind.MethodSignature - ) { - return getJsDocDeprecation(symbol); - } - } - - // Or it could be a ClassDeclaration or a variable set to a ClassExpression. - const symbolAtLocation = - symbol && checker.getTypeOfSymbolAtLocation(symbol, tsNode).getSymbol(); - - return symbolAtLocation && - tsutils.isSymbolFlagSet(symbolAtLocation, ts.SymbolFlags.Class) - ? getJsDocDeprecation(symbolAtLocation) - : undefined; - } - - function getSymbol( - node: IdentifierLike, - ): ts.Signature | ts.Symbol | undefined { - if (node.parent.type === AST_NODE_TYPES.Property) { - return services - .getTypeAtLocation(node.parent.parent) - .getProperty(node.name); + const symbol = services.getSymbolAtLocation(node); + const aliasedSymbol = + symbol !== undefined && + tsutils.isSymbolFlagSet(symbol, ts.SymbolFlags.Alias) + ? checker.getAliasedSymbol(symbol) + : symbol; + const symbolDeclarationKind = aliasedSymbol?.declarations?.[0].kind; + // Properties with function-like types have "deprecated" jsdoc + // on their symbols, not on their signatures: + // + // interface Props { + // /** @deprecated */ + // property: () => 'foo' + // ^symbol^ ^signature^ + // } + if ( + symbolDeclarationKind !== ts.SyntaxKind.MethodDeclaration && + symbolDeclarationKind !== ts.SyntaxKind.FunctionDeclaration && + symbolDeclarationKind !== ts.SyntaxKind.MethodSignature + ) { + return ( + searchForDeprecationInAliasesChain(symbol, true) ?? + getJsDocDeprecation(signature) ?? + getJsDocDeprecation(aliasedSymbol) + ); } - - return services.getSymbolAtLocation(node); + return ( + searchForDeprecationInAliasesChain( + symbol, + // Here we're working with a function declaration or method. + // Both can have 1 or more overloads, each overload creates one + // ts.Declaration which is placed in symbol.declarations. + // + // Imagine the following code: + // + // function foo(): void + // /** @deprecated Some Reason */ + // function foo(arg: string): void + // function foo(arg?: string): void {} + // + // foo() // <- foo is our symbol + // + // If we call getJsDocDeprecation(checker.getAliasedSymbol(symbol)), + // we get 'Some Reason', but after all, we are calling foo with + // a signature that is not deprecated! + // It works this way because symbol.getJsDocTags returns tags from + // all symbol declarations combined into one array. And AFAIK there is + // no publicly exported TS function that can tell us if a particular + // declaration is deprecated or not. + // + // So, in case of function and method declarations, we don't check original + // aliased symbol, but rely on the getJsDocDeprecation(signature) call below. + false, + ) ?? getJsDocDeprecation(signature) + ); } function getDeprecationReason(node: IdentifierLike): string | undefined { const callLikeNode = getCallLikeNode(node); - return callLikeNode - ? getCallLikeDeprecation(callLikeNode) - : getJsDocDeprecation(getSymbol(node)); + if (callLikeNode) { + return getCallLikeDeprecation(callLikeNode); + } + if (node.parent.type === AST_NODE_TYPES.Property) { + return getJsDocDeprecation( + services.getTypeAtLocation(node.parent.parent).getProperty(node.name), + ); + } + return searchForDeprecationInAliasesChain( + services.getSymbolAtLocation(node), + true, + ); } function checkIdentifier(node: IdentifierLike): void { diff --git a/packages/eslint-plugin/tests/fixtures/deprecated.ts b/packages/eslint-plugin/tests/fixtures/deprecated.ts new file mode 100644 index 000000000000..2302eabd3f54 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/deprecated.ts @@ -0,0 +1,42 @@ +/** @deprecated */ +export class DeprecatedClass { + /** @deprecated */ + foo: string = ''; +} +/** @deprecated */ +export const deprecatedVariable = 1; +/** @deprecated */ +export function deprecatedFunction(): void {} +class NormalClass {} +const normalVariable = 1; +function normalFunction(): void; +function normalFunction(arg: string): void; +function normalFunction(arg?: string): void {} +function deprecatedFunctionWithOverloads(): void; +/** @deprecated */ +function deprecatedFunctionWithOverloads(arg: string): void; +function deprecatedFunctionWithOverloads(arg?: string): void {} +export class ClassWithDeprecatedConstructor { + constructor(); + /** @deprecated */ + constructor(arg: string); + constructor(arg?: string) {} +} +export { + /** @deprecated */ + NormalClass, + /** @deprecated */ + normalVariable, + /** @deprecated */ + normalFunction, + deprecatedFunctionWithOverloads, + /** @deprecated Reason */ + deprecatedFunctionWithOverloads as reexportedDeprecatedFunctionWithOverloads, + /** @deprecated Reason */ + ClassWithDeprecatedConstructor as ReexportedClassWithDeprecatedConstructor, +}; + +/** @deprecated */ +export default { + foo: 1, +}; diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.json b/packages/eslint-plugin/tests/fixtures/tsconfig.json index c16815aaf1ac..a0fc993b1f48 100644 --- a/packages/eslint-plugin/tests/fixtures/tsconfig.json +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.json @@ -11,6 +11,7 @@ "include": [ "file.ts", "consistent-type-exports.ts", + "deprecated.ts", "mixed-enums-decl.ts", "react.tsx", "var-declaration.ts" diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json b/packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json new file mode 100644 index 000000000000..b0fcd9a65b92 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.moduleResolution-node16.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "module": "node16", + "moduleResolution": "node16" + } +} diff --git a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts index bdfe10135349..e8451d82b2cb 100644 --- a/packages/eslint-plugin/tests/rules/no-deprecated.test.ts +++ b/packages/eslint-plugin/tests/rules/no-deprecated.test.ts @@ -100,6 +100,26 @@ ruleTester.run('no-deprecated', rule, { a('b'); `, + ` + import { deprecatedFunctionWithOverloads } from './deprecated'; + + const foo = deprecatedFunctionWithOverloads(); + `, + ` + import * as imported from './deprecated'; + + const foo = imported.deprecatedFunctionWithOverloads(); + `, + ` + import { ClassWithDeprecatedConstructor } from './deprecated'; + + const foo = new ClassWithDeprecatedConstructor(); + `, + ` + import * as imported from './deprecated'; + + const foo = new imported.ClassWithDeprecatedConstructor(); + `, ` class A { a(value: 'b'): void; @@ -109,6 +129,16 @@ ruleTester.run('no-deprecated', rule, { declare const foo: A; foo.a('b'); `, + ` + const A = class { + /** @deprecated */ + constructor(); + constructor(arg: string); + constructor(arg?: string) {} + }; + + new A('a'); + `, ` type A = { (value: 'b'): void; @@ -207,6 +237,22 @@ ruleTester.run('no-deprecated', rule, { const [{ anchor = 'bar' }] = x; `, 'function fn(/** @deprecated */ foo = 4) {}', + { + code: ` + async function fn() { + const d = await import('./deprecated.js'); + d.default; + } + `, + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + projectService: false, + project: './tsconfig.moduleResolution-node16.json', + }, + }, + }, + 'call();', ], invalid: [ { @@ -677,13 +723,35 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + const A = class { + /** @deprecated */ + constructor(); + constructor(arg: string); + constructor(arg?: string) {} + }; + + new A(); + `, + errors: [ + { + column: 13, + endColumn: 14, + line: 9, + endLine: 9, + data: { name: 'A' }, + messageId: 'deprecated', + }, + ], + }, { code: ` declare const A: { /** @deprecated */ new (): string; }; - + new A(); `, errors: [ @@ -816,7 +884,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -839,7 +906,6 @@ ruleTester.run('no-deprecated', rule, { a.b(); `, - only: false, errors: [ { column: 11, @@ -859,12 +925,11 @@ ruleTester.run('no-deprecated', rule, { return ''; } } - + declare const a: A; - + a.b(); `, - only: false, errors: [ { column: 11, @@ -1584,5 +1649,696 @@ ruleTester.run('no-deprecated', rule, { }, ], }, + { + code: ` + import { DeprecatedClass } from './deprecated'; + + const foo = new DeprecatedClass(); + `, + errors: [ + { + column: 25, + endColumn: 40, + line: 4, + endLine: 4, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { DeprecatedClass } from './deprecated'; + + declare function inject(something: new () => unknown): void; + + inject(DeprecatedClass); + `, + errors: [ + { + column: 16, + endColumn: 31, + line: 6, + endLine: 6, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { deprecatedVariable } from './deprecated'; + + const foo = deprecatedVariable; + `, + errors: [ + { + column: 21, + endColumn: 39, + line: 4, + endLine: 4, + data: { name: 'deprecatedVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { DeprecatedClass } from './deprecated'; + + declare const x: DeprecatedClass; + + const { foo } = x; + `, + errors: [ + { + column: 26, + endColumn: 41, + line: 4, + endLine: 4, + data: { name: 'DeprecatedClass' }, + messageId: 'deprecated', + }, + { + column: 17, + endColumn: 20, + line: 6, + endLine: 6, + data: { name: 'foo' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { deprecatedFunction } from './deprecated'; + + deprecatedFunction(); + `, + errors: [ + { + column: 9, + endColumn: 27, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = new imported.NormalClass(); + `, + errors: [ + { + column: 34, + endColumn: 45, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { NormalClass } from './deprecated'; + + const foo = new NormalClass(); + `, + errors: [ + { + column: 25, + endColumn: 36, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.NormalClass; + `, + errors: [ + { + column: 30, + endColumn: 41, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { NormalClass } from './deprecated'; + + const foo = NormalClass; + `, + errors: [ + { + column: 21, + endColumn: 32, + line: 4, + endLine: 4, + data: { name: 'NormalClass' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { normalVariable } from './deprecated'; + + const foo = normalVariable; + `, + errors: [ + { + column: 21, + endColumn: 35, + line: 4, + endLine: 4, + data: { name: 'normalVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.normalVariable; + `, + errors: [ + { + column: 30, + endColumn: 44, + line: 4, + endLine: 4, + data: { name: 'normalVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { normalVariable } = imported; + `, + errors: [ + { + column: 17, + endColumn: 31, + line: 4, + endLine: 4, + data: { name: 'normalVariable' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { normalFunction } from './deprecated'; + + const foo = normalFunction; + `, + errors: [ + { + column: 21, + endColumn: 35, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.normalFunction; + `, + errors: [ + { + column: 30, + endColumn: 44, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { normalFunction } = imported; + `, + errors: [ + { + column: 17, + endColumn: 31, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { normalFunction } from './deprecated'; + + const foo = normalFunction(); + `, + errors: [ + { + column: 21, + endColumn: 35, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.normalFunction(); + `, + errors: [ + { + column: 30, + endColumn: 44, + line: 4, + endLine: 4, + data: { name: 'normalFunction' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { deprecatedFunctionWithOverloads } from './deprecated'; + + const foo = deprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 21, + endColumn: 52, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunctionWithOverloads' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.deprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 30, + endColumn: 61, + line: 4, + endLine: 4, + data: { name: 'deprecatedFunctionWithOverloads' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads; + `, + errors: [ + { + column: 21, + endColumn: 62, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads; + `, + errors: [ + { + column: 30, + endColumn: 71, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { reexportedDeprecatedFunctionWithOverloads } = imported; + `, + errors: [ + { + column: 17, + endColumn: 58, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads(); + `, + errors: [ + { + column: 21, + endColumn: 62, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads(); + `, + errors: [ + { + column: 30, + endColumn: 71, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { reexportedDeprecatedFunctionWithOverloads } from './deprecated'; + + const foo = reexportedDeprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 21, + endColumn: 62, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.reexportedDeprecatedFunctionWithOverloads('a'); + `, + errors: [ + { + column: 30, + endColumn: 71, + line: 4, + endLine: 4, + data: { + name: 'reexportedDeprecatedFunctionWithOverloads', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { ClassWithDeprecatedConstructor } from './deprecated'; + + const foo = new ClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 25, + endColumn: 55, + line: 4, + endLine: 4, + data: { name: 'ClassWithDeprecatedConstructor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = new imported.ClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 34, + endColumn: 64, + line: 4, + endLine: 4, + data: { name: 'ClassWithDeprecatedConstructor' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor; + `, + errors: [ + { + column: 21, + endColumn: 61, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor; + `, + errors: [ + { + column: 30, + endColumn: 70, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const { ReexportedClassWithDeprecatedConstructor } = imported; + `, + errors: [ + { + column: 17, + endColumn: 57, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor(); + `, + errors: [ + { + column: 21, + endColumn: 61, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor(); + `, + errors: [ + { + column: 30, + endColumn: 70, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import { ReexportedClassWithDeprecatedConstructor } from './deprecated'; + + const foo = ReexportedClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 21, + endColumn: 61, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import * as imported from './deprecated'; + + const foo = imported.ReexportedClassWithDeprecatedConstructor('a'); + `, + errors: [ + { + column: 30, + endColumn: 70, + line: 4, + endLine: 4, + data: { + name: 'ReexportedClassWithDeprecatedConstructor', + reason: 'Reason', + }, + messageId: 'deprecatedWithReason', + }, + ], + }, + { + code: ` + import imported from './deprecated'; + + imported; + `, + errors: [ + { + column: 9, + endColumn: 17, + line: 4, + endLine: 4, + data: { name: 'imported' }, + messageId: 'deprecated', + }, + ], + }, + { + code: ` + async function fn() { + const d = await import('./deprecated.js'); + d.default.default; + } + `, + languageOptions: { + parserOptions: { + tsconfigRootDir: rootDir, + projectService: false, + project: './tsconfig.moduleResolution-node16.json', + }, + }, + errors: [ + { + column: 21, + endColumn: 28, + line: 4, + endLine: 4, + data: { name: 'default' }, + messageId: 'deprecated', + }, + ], + }, ], }); From 8293546583e1aaf47ee70637849b5d7a990fff56 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 23 Sep 2024 10:11:48 -0600 Subject: [PATCH 11/16] fix(eslint-plugin): [no-confusing-non-null-assertion] check !in and !instanceof (#9994) * ban !in and !instanceof * lintfix * snapshots * cov --- .../rules/no-confusing-non-null-assertion.mdx | 19 +- .../rules/no-confusing-non-null-assertion.ts | 163 ++++++++++++++---- .../no-confusing-non-null-assertion.shot | 4 +- .../no-confusing-non-null-assertion.test.ts | 73 +++++++- 4 files changed, 219 insertions(+), 40 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx b/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx index 0ff48e14c926..01a47a9dde78 100644 --- a/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx +++ b/packages/eslint-plugin/docs/rules/no-confusing-non-null-assertion.mdx @@ -9,14 +9,27 @@ import TabItem from '@theme/TabItem'; > > See **https://typescript-eslint.io/rules/no-confusing-non-null-assertion** for documentation. -Using a non-null assertion (`!`) next to an assign or equals check (`=` or `==` or `===`) creates code that is confusing as it looks similar to a not equals check (`!=` `!==`). +Using a non-null assertion (`!`) next to an assignment or equality check (`=` or `==` or `===`) creates code that is confusing as it looks similar to an inequality check (`!=` `!==`). ```typescript -a! == b; // a non-null assertions(`!`) and an equals test(`==`) +a! == b; // a non-null assertion(`!`) and an equals test(`==`) a !== b; // not equals test(`!==`) -a! === b; // a non-null assertions(`!`) and an triple equals test(`===`) +a! === b; // a non-null assertion(`!`) and a triple equals test(`===`) ``` +Using a non-null assertion (`!`) next to an in test (`in`) or an instanceof test (`instanceof`) creates code that is confusing since it may look like the operator is negated, but it is actually not. + +{/* prettier-ignore */} +```typescript +a! in b; // a non-null assertion(`!`) and an in test(`in`) +a !in b; // also a non-null assertion(`!`) and an in test(`in`) +!(a in b); // a negated in test + +a! instanceof b; // a non-null assertion(`!`) and an instanceof test(`instanceof`) +a !instanceof b; // also a non-null assertion(`!`) and an instanceof test(`instanceof`) +!(a instanceof b); // a negated instanceof test +```` + This rule flags confusing `!` assertions and suggests either removing them or wrapping the asserted expression in `()` parenthesis. ## Examples diff --git a/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts index 9caf777aabad..160827fc5a32 100644 --- a/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-confusing-non-null-assertion.ts @@ -1,9 +1,36 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; +import type { + ReportDescriptor, + RuleFix, +} from '@typescript-eslint/utils/ts-eslint'; import { createRule } from '../util'; -export default createRule({ +type MessageId = + | 'confusingEqual' + | 'confusingAssign' + | 'confusingOperator' + | 'notNeedInEqualTest' + | 'notNeedInAssign' + | 'notNeedInOperator' + | 'wrapUpLeft'; + +const confusingOperators = new Set([ + '=', + '==', + '===', + 'in', + 'instanceof', +] as const); +type ConfusingOperator = + typeof confusingOperators extends Set ? T : never; + +function isConfusingOperator(operator: string): operator is ConfusingOperator { + return confusingOperators.has(operator as ConfusingOperator); +} + +export default createRule<[], MessageId>({ name: 'no-confusing-non-null-assertion', meta: { type: 'problem', @@ -15,35 +42,63 @@ export default createRule({ hasSuggestions: true, messages: { confusingEqual: - 'Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b".', + 'Confusing combination of non-null assertion and equality test like `a! == b`, which looks very similar to `a !== b`.', confusingAssign: - 'Confusing combinations of non-null assertion and equal test like "a! = b", which looks very similar to not equal "a != b".', - notNeedInEqualTest: 'Unnecessary non-null assertion (!) in equal test.', + 'Confusing combination of non-null assertion and assignment like `a! = b`, which looks very similar to `a != b`.', + confusingOperator: + 'Confusing combination of non-null assertion and `{{operator}}` operator like `a! {{operator}} b`, which might be misinterpreted as `!(a {{operator}} b)`.', + + notNeedInEqualTest: + 'Remove unnecessary non-null assertion (!) in equality test.', notNeedInAssign: - 'Unnecessary non-null assertion (!) in assignment left hand.', + 'Remove unnecessary non-null assertion (!) in assignment left-hand side.', + + notNeedInOperator: + 'Remove possibly unnecessary non-null assertion (!) in the left operand of the `{{operator}}` operator.', + wrapUpLeft: - 'Wrap up left hand to avoid putting non-null assertion "!" and "=" together.', + 'Wrap the left-hand side in parentheses to avoid confusion with "{{operator}}" operator.', }, schema: [], }, defaultOptions: [], create(context) { + function confusingOperatorToMessageData( + operator: ConfusingOperator, + ): Pick, 'messageId' | 'data'> { + switch (operator) { + case '=': + return { + messageId: 'confusingAssign', + }; + case '==': + case '===': + return { + messageId: 'confusingEqual', + }; + case 'in': + case 'instanceof': + return { + messageId: 'confusingOperator', + data: { operator }, + }; + // istanbul ignore next + default: + operator satisfies never; + throw new Error(`Unexpected operator ${operator as string}`); + } + } + return { 'BinaryExpression, AssignmentExpression'( node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression, ): void { - function isLeftHandPrimaryExpression( - node: TSESTree.Expression | TSESTree.PrivateIdentifier, - ): boolean { - return node.type === AST_NODE_TYPES.TSNonNullExpression; - } + const operator = node.operator; - if ( - node.operator === '==' || - node.operator === '===' || - node.operator === '=' - ) { - const isAssign = node.operator === '='; + if (isConfusingOperator(operator)) { + // Look for a non-null assertion as the last token on the left hand side. + // That way, we catch things like `1 + two! === 3`, even though the left + // hand side isn't a non-null assertion AST node. const leftHandFinalToken = context.sourceCode.getLastToken(node.left); const tokenAfterLeft = context.sourceCode.getTokenAfter(node.left); if ( @@ -51,32 +106,63 @@ export default createRule({ leftHandFinalToken.value === '!' && tokenAfterLeft?.value !== ')' ) { - if (isLeftHandPrimaryExpression(node.left)) { + if (node.left.type === AST_NODE_TYPES.TSNonNullExpression) { + let suggestions: TSESLint.SuggestionReportDescriptor[]; + switch (operator) { + case '=': + suggestions = [ + { + messageId: 'notNeedInAssign', + fix: (fixer): RuleFix => fixer.remove(leftHandFinalToken), + }, + ]; + break; + + case '==': + case '===': + suggestions = [ + { + messageId: 'notNeedInEqualTest', + fix: (fixer): RuleFix => fixer.remove(leftHandFinalToken), + }, + ]; + break; + + case 'in': + case 'instanceof': + suggestions = [ + { + messageId: 'notNeedInOperator', + data: { operator }, + fix: (fixer): RuleFix => fixer.remove(leftHandFinalToken), + }, + { + messageId: 'wrapUpLeft', + data: { operator }, + fix: wrapUpLeftFixer(node), + }, + ]; + break; + + // istanbul ignore next + default: + operator satisfies never; + return; + } context.report({ node, - messageId: isAssign ? 'confusingAssign' : 'confusingEqual', - suggest: [ - { - messageId: isAssign - ? 'notNeedInAssign' - : 'notNeedInEqualTest', - fix: (fixer): TSESLint.RuleFix[] => [ - fixer.remove(leftHandFinalToken), - ], - }, - ], + ...confusingOperatorToMessageData(operator), + suggest: suggestions, }); } else { context.report({ node, - messageId: isAssign ? 'confusingAssign' : 'confusingEqual', + ...confusingOperatorToMessageData(operator), suggest: [ { messageId: 'wrapUpLeft', - fix: (fixer): TSESLint.RuleFix[] => [ - fixer.insertTextBefore(node.left, '('), - fixer.insertTextAfter(node.left, ')'), - ], + data: { operator }, + fix: wrapUpLeftFixer(node), }, ], }); @@ -87,3 +173,12 @@ export default createRule({ }; }, }); + +function wrapUpLeftFixer( + node: TSESTree.AssignmentExpression | TSESTree.BinaryExpression, +): TSESLint.ReportFixFunction { + return (fixer): TSESLint.RuleFix[] => [ + fixer.insertTextBefore(node.left, '('), + fixer.insertTextAfter(node.left, ')'), + ]; +} diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-confusing-non-null-assertion.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-confusing-non-null-assertion.shot index 074f8f721db5..fc3fe7bcc1be 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-confusing-non-null-assertion.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-confusing-non-null-assertion.shot @@ -10,9 +10,9 @@ interface Foo { const foo: Foo = getFoo(); const isEqualsBar = foo.bar! == 'hello'; - ~~~~~~~~~~~~~~~~~~~ Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b". + ~~~~~~~~~~~~~~~~~~~ Confusing combination of non-null assertion and equality test like \`a! == b\`, which looks very similar to \`a !== b\`. const isEqualsNum = 1 + foo.num! == 2; - ~~~~~~~~~~~~~~~~~ Confusing combinations of non-null assertion and equal test like "a! == b", which looks very similar to not equal "a !== b". + ~~~~~~~~~~~~~~~~~ Confusing combination of non-null assertion and equality test like \`a! == b\`, which looks very similar to \`a !== b\`. " `; diff --git a/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts index 19f3cfec601d..6cdefc9b6c59 100644 --- a/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-confusing-non-null-assertion.test.ts @@ -3,7 +3,7 @@ /* eslint "@typescript-eslint/internal/plugin-test-formatting": ["error", { formatWithPrettier: false }] */ /* eslint-enable eslint-comments/no-use */ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-confusing-non-null-assertion'; @@ -18,6 +18,8 @@ ruleTester.run('no-confusing-non-null-assertion', rule, { 'a != b;', '(a + b!) == c;', '(a + b!) = c;', + '(a + b!) in c;', + '(a || b!) instanceof c;', ], invalid: [ { @@ -148,5 +150,74 @@ ruleTester.run('no-confusing-non-null-assertion', rule, { }, ], }, + { + code: 'a! in b;', + errors: [ + { + messageId: 'confusingOperator', + data: { operator: 'in' }, + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInOperator', + output: 'a in b;', + }, + { + messageId: 'wrapUpLeft', + output: '(a!) in b;', + }, + ], + }, + ], + }, + { + code: noFormat` +a !in b; + `, + errors: [ + { + messageId: 'confusingOperator', + data: { operator: 'in' }, + line: 2, + column: 1, + suggestions: [ + { + messageId: 'notNeedInOperator', + output: ` +a in b; + `, + }, + { + messageId: 'wrapUpLeft', + output: ` +(a !)in b; + `, + }, + ], + }, + ], + }, + { + code: 'a! instanceof b;', + errors: [ + { + messageId: 'confusingOperator', + data: { operator: 'instanceof' }, + line: 1, + column: 1, + suggestions: [ + { + messageId: 'notNeedInOperator', + output: 'a instanceof b;', + }, + { + messageId: 'wrapUpLeft', + output: '(a!) instanceof b;', + }, + ], + }, + ], + }, ], }); From e8555a078bf7bc8f63a0715ce520e9ce9b618cbb Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 23 Sep 2024 10:12:10 -0600 Subject: [PATCH 12/16] feat(eslint-plugin): [no-unsafe-call] check calls of Function (#10010) * [no-unsafe-call] check calls of Function * lint * weird edge cases * comment * naming * feedback --- .../docs/rules/no-unsafe-call.mdx | 28 +++ .../docs/rules/no-unsafe-function-type.mdx | 1 + .../eslint-plugin/src/rules/no-unsafe-call.ts | 52 ++++- .../no-unsafe-call.shot | 25 ++- .../tests/rules/no-unsafe-call.test.ts | 177 ++++++++++++++++++ .../rules/no-unsafe-function-type.test.ts | 8 +- .../website/src/components/lib/parseConfig.ts | 2 +- 7 files changed, 278 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx index 3e56c1e0f092..6fc46d6cbf18 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx +++ b/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx @@ -59,6 +59,34 @@ String.raw`foo`; +## The Unsafe `Function` Type + +The `Function` type is behaves almost identically to `any` when called, so this rule also disallows calling values of type `Function`. + + + + +```ts +const f: Function = () => {}; +f(); +``` + + + + +Note that whereas [no-unsafe-function-type](./no-unsafe-function-type.mdx) helps prevent the _creation_ of `Function` types, this rule helps prevent the unsafe _use_ of `Function` types, which may creep into your codebase without explicitly referencing the `Function` type at all. +See, for example, the following code: + +```ts +function unsafe(maybeFunction: unknown): string { + if (typeof maybeFunction === 'function') { + // TypeScript allows this, but it's completely unsound. + return maybeFunction('call', 'with', 'any', 'args'); + } + // etc +} +``` + ## When Not To Use It If your codebase has many existing `any`s or areas of unsafe code, it may be difficult to enable this rule. diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx index ea7b60794e4e..ee1a84395d8d 100644 --- a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx +++ b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx @@ -60,4 +60,5 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates - [`no-empty-object-type`](./no-empty-object-type.mdx) - [`no-restricted-types`](./no-restricted-types.mdx) +- [`no-unsafe-call`](./no-unsafe-call.mdx) - [`no-wrapper-object-types`](./no-wrapper-object-types.mdx) diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts index 6bdec17a4427..8149f2736a15 100644 --- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts +++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts @@ -6,6 +6,7 @@ import { getConstrainedTypeAtLocation, getParserServices, getThisExpression, + isBuiltinSymbolLike, isTypeAnyType, } from '../util'; @@ -25,13 +26,13 @@ export default createRule<[], MessageIds>({ requiresTypeChecking: true, }, messages: { - unsafeCall: 'Unsafe call of an {{type}} typed value.', + unsafeCall: 'Unsafe call of a(n) {{type}} typed value.', unsafeCallThis: [ - 'Unsafe call of an `any` typed value. `this` is typed as `any`.', + 'Unsafe call of a(n) {{type}} typed value. `this` is typed as {{type}}.', 'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.', ].join('\n'), - unsafeNew: 'Unsafe construction of an any type value.', - unsafeTemplateTag: 'Unsafe any typed template tag.', + unsafeNew: 'Unsafe construction of a(n) {{type}} typed value.', + unsafeTemplateTag: 'Unsafe use of a(n) {{type}} typed template tag.', }, schema: [], }, @@ -74,6 +75,49 @@ export default createRule<[], MessageIds>({ type: isErrorType ? '`error` type' : '`any`', }, }); + return; + } + + if (isBuiltinSymbolLike(services.program, type, 'Function')) { + // this also matches subtypes of `Function`, like `interface Foo extends Function {}`. + // + // For weird TS reasons that I don't understand, these are + // + // safe to construct if: + // - they have at least one call signature _that is not void-returning_, + // - OR they have at least one construct signature. + // + // safe to call (including as template) if: + // - they have at least one call signature + // - OR they have at least one construct signature. + + const constructSignatures = type.getConstructSignatures(); + if (constructSignatures.length > 0) { + return; + } + + const callSignatures = type.getCallSignatures(); + if (messageId === 'unsafeNew') { + if ( + callSignatures.some( + signature => + !tsutils.isIntrinsicVoidType(signature.getReturnType()), + ) + ) { + return; + } + } else if (callSignatures.length > 0) { + return; + } + + context.report({ + node: reportingNode, + messageId, + data: { + type: '`Function`', + }, + }); + return; } } diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot index bfc1388d6cf9..7b3ee29e7a93 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot @@ -7,24 +7,24 @@ declare const anyVar: any; declare const nestedAny: { prop: any }; anyVar(); -~~~~~~ Unsafe call of an \`any\` typed value. +~~~~~~ Unsafe call of a(n) \`any\` typed value. anyVar.a.b(); -~~~~~~~~~~ Unsafe call of an \`any\` typed value. +~~~~~~~~~~ Unsafe call of a(n) \`any\` typed value. nestedAny.prop(); -~~~~~~~~~~~~~~ Unsafe call of an \`any\` typed value. +~~~~~~~~~~~~~~ Unsafe call of a(n) \`any\` typed value. nestedAny.prop['a'](); -~~~~~~~~~~~~~~~~~~~ Unsafe call of an \`any\` typed value. +~~~~~~~~~~~~~~~~~~~ Unsafe call of a(n) \`any\` typed value. new anyVar(); -~~~~~~~~~~~~ Unsafe construction of an any type value. +~~~~~~~~~~~~ Unsafe construction of a(n) \`any\` typed value. new nestedAny.prop(); -~~~~~~~~~~~~~~~~~~~~ Unsafe construction of an any type value. +~~~~~~~~~~~~~~~~~~~~ Unsafe construction of a(n) \`any\` typed value. anyVar\`foo\`; -~~~~~~ Unsafe any typed template tag. +~~~~~~ Unsafe use of a(n) \`any\` typed template tag. nestedAny.prop\`foo\`; -~~~~~~~~~~~~~~ Unsafe any typed template tag. +~~~~~~~~~~~~~~ Unsafe use of a(n) \`any\` typed template tag. " `; @@ -44,3 +44,12 @@ new Map(); String.raw\`foo\`; " `; + +exports[`Validating rule docs no-unsafe-call.mdx code examples ESLint output 3`] = ` +"Incorrect + +const f: Function = () => {}; +f(); +~ Unsafe call of a(n) \`Function\` typed value. +" +`; diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts index 1a26a4ef3d33..8baf831f96e6 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts @@ -44,6 +44,52 @@ function foo(x: { a?: () => void }) { x(); } `, + ` + // create a scope since it's illegal to declare a duplicate identifier + // 'Function' in the global script scope. + { + type Function = () => void; + const notGlobalFunctionType: Function = (() => {}) as Function; + notGlobalFunctionType(); + } + `, + ` +interface SurprisinglySafe extends Function { + (): string; +} +declare const safe: SurprisinglySafe; +safe(); + `, + ` +interface CallGoodConstructBad extends Function { + (): void; +} +declare const safe: CallGoodConstructBad; +safe(); + `, + ` +interface ConstructSignatureMakesSafe extends Function { + new (): ConstructSignatureMakesSafe; +} +declare const safe: ConstructSignatureMakesSafe; +new safe(); + `, + ` +interface SafeWithNonVoidCallSignature extends Function { + (): void; + (x: string): string; +} +declare const safe: SafeWithNonVoidCallSignature; +safe(); + `, + // Function has type FunctionConstructor, so it's not within this rule's purview + ` + new Function('lol'); + `, + // Function has type FunctionConstructor, so it's not within this rule's purview + ` + Function('lol'); + `, ], invalid: [ { @@ -251,5 +297,136 @@ value(); }, ], }, + { + code: ` +const t: Function = () => {}; +t(); + `, + errors: [ + { + messageId: 'unsafeCall', + line: 3, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +const f: Function = () => {}; +f\`oo\`; + `, + errors: [ + { + messageId: 'unsafeTemplateTag', + line: 3, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +declare const maybeFunction: unknown; +if (typeof maybeFunction === 'function') { + maybeFunction('call', 'with', 'any', 'args'); +} + `, + errors: [ + { + messageId: 'unsafeCall', + line: 4, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +interface Unsafe extends Function {} +declare const unsafe: Unsafe; +unsafe(); + `, + errors: [ + { + messageId: 'unsafeCall', + line: 4, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +interface Unsafe extends Function {} +declare const unsafe: Unsafe; +unsafe\`bad\`; + `, + errors: [ + { + messageId: 'unsafeTemplateTag', + line: 4, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +interface Unsafe extends Function {} +declare const unsafe: Unsafe; +new unsafe(); + `, + errors: [ + { + messageId: 'unsafeNew', + line: 4, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +interface UnsafeToConstruct extends Function { + (): void; +} +declare const unsafe: UnsafeToConstruct; +new unsafe(); + `, + errors: [ + { + messageId: 'unsafeNew', + line: 6, + data: { + type: '`Function`', + }, + }, + ], + }, + { + code: ` +interface StillUnsafe extends Function { + property: string; +} +declare const unsafe: StillUnsafe; +unsafe(); + `, + errors: [ + { + messageId: 'unsafeCall', + line: 6, + data: { + type: '`Function`', + }, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts index 75c116a66aa5..ae5b96d1ad51 100644 --- a/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts @@ -9,8 +9,12 @@ ruleTester.run('no-unsafe-function-type', rule, { 'let value: () => void;', 'let value: (t: T) => T;', ` - type Function = () => void; - let value: Function; + // create a scope since it's illegal to declare a duplicate identifier + // 'Function' in the global script scope. + { + type Function = () => void; + let value: Function; + } `, ], invalid: [ diff --git a/packages/website/src/components/lib/parseConfig.ts b/packages/website/src/components/lib/parseConfig.ts index 2915e9f350db..29cd8d6f4a91 100644 --- a/packages/website/src/components/lib/parseConfig.ts +++ b/packages/website/src/components/lib/parseConfig.ts @@ -55,7 +55,7 @@ export function parseTSConfig(code?: string): TSConfig { const moduleRegexp = /(module\.exports\s*=)/g; function constrainedScopeEval(obj: string): unknown { - // eslint-disable-next-line @typescript-eslint/no-implied-eval + // eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call return new Function(` "use strict"; var module = { exports: {} }; From dc1c6d34445377ff34d2b1c6184eaa13085e2307 Mon Sep 17 00:00:00 2001 From: Kirk Waiblinger Date: Mon, 23 Sep 2024 10:15:20 -0600 Subject: [PATCH 13/16] docs: [no-unnecessary-type-parameters] add FAQ section (#9975) * [no-unnecessary-type-parameters] add FAQ section * wording * wording * update admonition link * test case * stuf * prettierer * test case formatting * log * mention 9735 * explain the equal false positives a little better * add implicit return type FAQ --- .../rules/no-unnecessary-type-parameters.mdx | 123 +++++++++++++++++- .../no-unnecessary-type-parameters.test.ts | 28 +++- 2 files changed, 149 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx b/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx index a11969f58f51..c27da7616a0f 100644 --- a/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx +++ b/packages/eslint-plugin/docs/rules/no-unnecessary-type-parameters.mdx @@ -19,7 +19,7 @@ At best unnecessary type parameters make code harder to read. At worst they can be used to disguise unsafe type assertions. :::warning -This rule was recently added, and has a surprising amount of hidden complexity compared to most of our rules. If you encounter unexpected behavior with it, please check closely the [Limitations](#limitations) section below and our [issue tracker](https://github.com/typescript-eslint/typescript-eslint/issues?q=is%3Aissue+no-unnecessary-type-parameters). +This rule was recently added, and has a surprising amount of hidden complexity compared to most of our rules. If you encounter unexpected behavior with it, please check closely the [Limitations](#limitations) and [FAQ](#faq) sections below and our [issue tracker](https://github.com/typescript-eslint/typescript-eslint/issues?q=is%3Aissue+no-unnecessary-type-parameters). If you don't see your case covered, please [reach out to us](https://typescript-eslint.io/contributing/issues)! ::: @@ -87,6 +87,127 @@ This is because the type parameter `T` relates multiple methods in the `T[]` tog Therefore, this rule won't report on type parameters used as a type argument. That includes type arguments given to global types such as `Array` (including the `T[]` shorthand and in tuples), `Map`, and `Set`. +## FAQ + +### The return type is only used as an input, so why isn't the rule reporting? + +One common reason that this might be the case is when the return type is not specified explicitly. +The rule uses uses type information to count implicit usages of the type parameter in the function signature, including in the inferred return type. +For example, the following function... + +```ts +function identity(arg: T) { + return arg; +} +``` + +...implicitly has a return type of `T`. Therefore, the type parameter `T` is used twice, and the rule will not report this function. + +For other reasons the rule might not be reporting, be sure to check the [Limitations section](#limitations) and other FAQs. + +### I'm using the type parameter inside the function, so why is the rule reporting? + +You might be surprised to that the rule reports on a function like this: + +```ts +function log(string1: T): void { + const string2: T = string1; + console.log(string2); +} +``` + +After all, the type parameter `T` relates the input `string1` and the local variable `string2`, right? +However, this usage is unnecessary, since we can achieve the same results by replacing all usages of the type parameter with its constraint. +That is to say, the function can always be rewritten as: + +```ts +function log(string1: string): void { + const string2: string = string1; + console.log(string2); +} +``` + +Therefore, this rule only counts usages of a type parameter in the _signature_ of a function, method, or class, but not in the implementation. See also [#9735](https://github.com/typescript-eslint/typescript-eslint/issues/9735) + +### Why am I getting TypeScript errors saying "Object literal may only specify known properties" after removing an unnecessary type parameter? + +Suppose you have a situation like the following, which will trigger the rule to report. + +```ts +interface SomeProperties { + foo: string; +} + +// T is only used once, so the rule will report. +function serialize(x: T): string { + return JSON.stringify(x); +} + +serialize({ foo: 'bar', anotherProperty: 'baz' }); +``` + +If we remove the unnecessary type parameter, we'll get an error: + +```ts +function serialize(x: SomeProperties): string { + return JSON.stringify(x); +} + +// TS Error: Object literal may only specify known properties, and 'anotherProperty' does not exist in type 'SomeProperties'. +serialize({ foo: 'bar', anotherProperty: 'baz' }); +``` + +This is because TypeScript figures it's _usually_ an error to explicitly provide excess properties in a location that expects a specific type. +See [the TypeScript handbook's section on excess property checks](https://www.typescriptlang.org/docs/handbook/2/objects.html#excess-property-checks) for further discussion. + +To resolve this, you have two approaches to choose from. + +1. If it doesn't make sense to accept excess properties in your function, you'll want to fix the errors at the call sites. Usually, you can simply remove any excess properties where the function is called. +2. Otherwise, if you do want your function to accept excess properties, you can modify the parameter type in order to allow excess properties explicitly by using an [index signature](https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures): + + ```ts + interface SomeProperties { + foo: string; + + // This allows any other properties. + // You may wish to make these types more specific according to your use case. + [key: PropertKey]: unknown; + } + + function serialize(x: SomeProperties): string { + return JSON.stringify(x); + } + + // No error! + serialize({ foo: 'bar', anotherProperty: 'baz' }); + ``` + +Which solution is appropriate is a case-by-case decision, depending on the intended use case of your function. + +### I have a complex scenario that is reported by the rule, but I can't see how to remove the type parameter. What should I do? + +Sometimes, you may be able to rewrite the code by reaching for some niche TypeScript features, such as [the `NoInfer` utility type](https://www.typescriptlang.org/docs/handbook/utility-types.html#noinfertype) (see [#9751](https://github.com/typescript-eslint/typescript-eslint/issues/9751)). + +But, quite possibly, you've hit an edge case where the type is being used in a subtle way that the rule doesn't account for. +For example, the following arcane code is a way of testing whether two types are equal, and will be reported by the rule (see [#9709](https://github.com/typescript-eslint/typescript-eslint/issues/9709)): + +{/* prettier-ignore */} +```ts +type Compute = A extends Function ? A : { [K in keyof A]: Compute }; +type Equal = + (() => T1 extends Compute ? 1 : 2) extends + (() => T2 extends Compute ? 1 : 2) + ? true + : false; +``` + +In this case, the function types created within the `Equal` type are never expected to be assigned to; they're just created for the purpose of type system manipulations. +This usage is not what the rule is intended to analyze. + +Use eslint-disable comments as appropriate to suppress the rule in these kinds of cases. + +{/* TODO - include an FAQ entry regarding instantiation expressions once the conversation in https://github.com/typescript-eslint/typescript-eslint/pull/9536#discussion_r1705850744 is done */} + ## When Not To Use It This rule will report on functions that use type parameters solely to test types, for example: diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts index 78b4be3022de..1776e440a84d 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-parameters.test.ts @@ -1,4 +1,4 @@ -import { RuleTester } from '@typescript-eslint/rule-tester'; +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; import rule from '../../src/rules/no-unnecessary-type-parameters'; import { getFixturesRootDir } from '../RuleTester'; @@ -970,5 +970,31 @@ const f = ( }, ], }, + { + // This isn't actually an important test case. + // However, we use it as an example in the docs of code that is flagged, + // but shouldn't necessarily be. So, if you make a change to the rule logic + // that resolves this sort-of-false-positive, please update the docs + // accordingly. + // Original discussion in https://github.com/typescript-eslint/typescript-eslint/issues/9709 + code: noFormat` +type Compute = A extends Function ? A : { [K in keyof A]: Compute }; +type Equal = + (() => T1 extends Compute ? 1 : 2) extends + (() => T2 extends Compute ? 1 : 2) + ? true + : false; + `, + errors: [ + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T1', uses: 'used only once' }, + }, + { + messageId: 'sole', + data: { descriptor: 'function', name: 'T2', uses: 'used only once' }, + }, + ], + }, ], }); From d0e35d9897221896a80e33f5a0f96ec138a8911c Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Mon, 23 Sep 2024 16:26:15 +0000 Subject: [PATCH 14/16] feat(eslint-plugin): [consistent-type-exports] check `export *` exports to see if all exported members are types (#10006) * feat(eslint-plugin): [consistent-type-exports] check "export *" exports to see if all exported members are types * filter + length === some * lint --fix --- packages/ast-spec/src/declaration/spec.ts | 28 ++--- packages/ast-spec/src/element/spec.ts | 30 ++--- .../expression/AssignmentExpression/spec.ts | 2 +- .../src/expression/BinaryExpression/spec.ts | 2 +- .../ast-spec/src/expression/literal/spec.ts | 12 +- packages/ast-spec/src/expression/spec.ts | 68 +++++------ packages/ast-spec/src/index.ts | 94 +++++++-------- packages/ast-spec/src/jsx/spec.ts | 26 ++--- packages/ast-spec/src/parameter/spec.ts | 10 +- packages/ast-spec/src/special/spec.ts | 48 ++++---- packages/ast-spec/src/statement/spec.ts | 36 +++--- .../src/token/PunctuatorToken/spec.ts | 2 +- packages/ast-spec/src/token/spec.ts | 26 ++--- packages/ast-spec/src/type/spec.ts | 90 +++++++-------- .../src/rules/consistent-type-exports.ts | 109 ++++++++++++++++-- packages/eslint-plugin/src/util/index.ts | 2 +- .../index.ts} | 2 + .../type-only-exports.ts | 9 ++ .../type-only-reexport.ts | 13 +++ .../consistent-type-exports/value-reexport.ts | 1 + .../rules/consistent-type-exports.test.ts | 83 +++++++++++++ .../scope-manager/src/definition/index.ts | 2 +- packages/scope-manager/src/scope/index.ts | 2 +- packages/types/src/index.ts | 4 +- .../typescript-estree/src/ts-estree/index.ts | 4 +- packages/utils/src/eslint-utils/index.ts | 2 +- packages/utils/src/index.ts | 2 +- packages/utils/src/ts-eslint/index.ts | 12 +- 28 files changed, 457 insertions(+), 264 deletions(-) rename packages/eslint-plugin/tests/fixtures/{consistent-type-exports.ts => consistent-type-exports/index.ts} (80%) create mode 100644 packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-exports.ts create mode 100644 packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-reexport.ts create mode 100644 packages/eslint-plugin/tests/fixtures/consistent-type-exports/value-reexport.ts diff --git a/packages/ast-spec/src/declaration/spec.ts b/packages/ast-spec/src/declaration/spec.ts index 52ab7c72bab6..51e982c3089b 100644 --- a/packages/ast-spec/src/declaration/spec.ts +++ b/packages/ast-spec/src/declaration/spec.ts @@ -1,14 +1,14 @@ -export * from './ClassDeclaration/spec'; -export * from './ExportAllDeclaration/spec'; -export * from './ExportDefaultDeclaration/spec'; -export * from './ExportNamedDeclaration/spec'; -export * from './FunctionDeclaration/spec'; -export * from './ImportDeclaration/spec'; -export * from './TSDeclareFunction/spec'; -export * from './TSEnumDeclaration/spec'; -export * from './TSImportEqualsDeclaration/spec'; -export * from './TSInterfaceDeclaration/spec'; -export * from './TSModuleDeclaration/spec'; -export * from './TSNamespaceExportDeclaration/spec'; -export * from './TSTypeAliasDeclaration/spec'; -export * from './VariableDeclaration/spec'; +export type * from './ClassDeclaration/spec'; +export type * from './ExportAllDeclaration/spec'; +export type * from './ExportDefaultDeclaration/spec'; +export type * from './ExportNamedDeclaration/spec'; +export type * from './FunctionDeclaration/spec'; +export type * from './ImportDeclaration/spec'; +export type * from './TSDeclareFunction/spec'; +export type * from './TSEnumDeclaration/spec'; +export type * from './TSImportEqualsDeclaration/spec'; +export type * from './TSInterfaceDeclaration/spec'; +export type * from './TSModuleDeclaration/spec'; +export type * from './TSNamespaceExportDeclaration/spec'; +export type * from './TSTypeAliasDeclaration/spec'; +export type * from './VariableDeclaration/spec'; diff --git a/packages/ast-spec/src/element/spec.ts b/packages/ast-spec/src/element/spec.ts index 6edf6efe39af..5a3105246aa5 100644 --- a/packages/ast-spec/src/element/spec.ts +++ b/packages/ast-spec/src/element/spec.ts @@ -1,15 +1,15 @@ -export * from './AccessorProperty/spec'; -export * from './MethodDefinition/spec'; -export * from './Property/spec'; -export * from './PropertyDefinition/spec'; -export * from './SpreadElement/spec'; -export * from './StaticBlock/spec'; -export * from './TSAbstractAccessorProperty/spec'; -export * from './TSAbstractMethodDefinition/spec'; -export * from './TSAbstractPropertyDefinition/spec'; -export * from './TSCallSignatureDeclaration/spec'; -export * from './TSConstructSignatureDeclaration/spec'; -export * from './TSEnumMember/spec'; -export * from './TSIndexSignature/spec'; -export * from './TSMethodSignature/spec'; -export * from './TSPropertySignature/spec'; +export type * from './AccessorProperty/spec'; +export type * from './MethodDefinition/spec'; +export type * from './Property/spec'; +export type * from './PropertyDefinition/spec'; +export type * from './SpreadElement/spec'; +export type * from './StaticBlock/spec'; +export type * from './TSAbstractAccessorProperty/spec'; +export type * from './TSAbstractMethodDefinition/spec'; +export type * from './TSAbstractPropertyDefinition/spec'; +export type * from './TSCallSignatureDeclaration/spec'; +export type * from './TSConstructSignatureDeclaration/spec'; +export type * from './TSEnumMember/spec'; +export type * from './TSIndexSignature/spec'; +export type * from './TSMethodSignature/spec'; +export type * from './TSPropertySignature/spec'; diff --git a/packages/ast-spec/src/expression/AssignmentExpression/spec.ts b/packages/ast-spec/src/expression/AssignmentExpression/spec.ts index d4cfeea7ef28..ebbd420cb05e 100644 --- a/packages/ast-spec/src/expression/AssignmentExpression/spec.ts +++ b/packages/ast-spec/src/expression/AssignmentExpression/spec.ts @@ -4,7 +4,7 @@ import type { Expression } from '../../unions/Expression'; import type { ValueOf } from '../../utils'; import type { AssignmentOperatorToText } from './AssignmentOperatorToText'; -export * from './AssignmentOperatorToText'; +export type * from './AssignmentOperatorToText'; export interface AssignmentExpression extends BaseNode { left: Expression; diff --git a/packages/ast-spec/src/expression/BinaryExpression/spec.ts b/packages/ast-spec/src/expression/BinaryExpression/spec.ts index 9547483403bb..2c8b82fe0968 100644 --- a/packages/ast-spec/src/expression/BinaryExpression/spec.ts +++ b/packages/ast-spec/src/expression/BinaryExpression/spec.ts @@ -5,7 +5,7 @@ import type { Expression } from '../../unions/Expression'; import type { ValueOf } from '../../utils'; import type { BinaryOperatorToText } from './BinaryOperatorToText'; -export * from './BinaryOperatorToText'; +export type * from './BinaryOperatorToText'; export interface BinaryExpression extends BaseNode { left: Expression | PrivateIdentifier; diff --git a/packages/ast-spec/src/expression/literal/spec.ts b/packages/ast-spec/src/expression/literal/spec.ts index d40804b424e1..ea08cb1b1259 100644 --- a/packages/ast-spec/src/expression/literal/spec.ts +++ b/packages/ast-spec/src/expression/literal/spec.ts @@ -1,6 +1,6 @@ -export * from './BigIntLiteral/spec'; -export * from './BooleanLiteral/spec'; -export * from './NullLiteral/spec'; -export * from './NumberLiteral/spec'; -export * from './RegExpLiteral/spec'; -export * from './StringLiteral/spec'; +export type * from './BigIntLiteral/spec'; +export type * from './BooleanLiteral/spec'; +export type * from './NullLiteral/spec'; +export type * from './NumberLiteral/spec'; +export type * from './RegExpLiteral/spec'; +export type * from './StringLiteral/spec'; diff --git a/packages/ast-spec/src/expression/spec.ts b/packages/ast-spec/src/expression/spec.ts index 5162ebf49116..5540ebdf10c7 100644 --- a/packages/ast-spec/src/expression/spec.ts +++ b/packages/ast-spec/src/expression/spec.ts @@ -1,34 +1,34 @@ -export * from './ArrayExpression/spec'; -export * from './ArrowFunctionExpression/spec'; -export * from './AssignmentExpression/spec'; -export * from './AwaitExpression/spec'; -export * from './BinaryExpression/spec'; -export * from './CallExpression/spec'; -export * from './ChainExpression/spec'; -export * from './ClassExpression/spec'; -export * from './ConditionalExpression/spec'; -export * from './FunctionExpression/spec'; -export * from './Identifier/spec'; -export * from './ImportExpression/spec'; -export * from './JSXElement/spec'; -export * from './JSXFragment/spec'; -export * from './literal/spec'; -export * from './LogicalExpression/spec'; -export * from './MemberExpression/spec'; -export * from './MetaProperty/spec'; -export * from './NewExpression/spec'; -export * from './ObjectExpression/spec'; -export * from './SequenceExpression/spec'; -export * from './Super/spec'; -export * from './TaggedTemplateExpression/spec'; -export * from './TemplateLiteral/spec'; -export * from './ThisExpression/spec'; -export * from './TSAsExpression/spec'; -export * from './TSEmptyBodyFunctionExpression/spec'; -export * from './TSInstantiationExpression/spec'; -export * from './TSNonNullExpression/spec'; -export * from './TSSatisfiesExpression/spec'; -export * from './TSTypeAssertion/spec'; -export * from './UnaryExpression/spec'; -export * from './UpdateExpression/spec'; -export * from './YieldExpression/spec'; +export type * from './ArrayExpression/spec'; +export type * from './ArrowFunctionExpression/spec'; +export type * from './AssignmentExpression/spec'; +export type * from './AwaitExpression/spec'; +export type * from './BinaryExpression/spec'; +export type * from './CallExpression/spec'; +export type * from './ChainExpression/spec'; +export type * from './ClassExpression/spec'; +export type * from './ConditionalExpression/spec'; +export type * from './FunctionExpression/spec'; +export type * from './Identifier/spec'; +export type * from './ImportExpression/spec'; +export type * from './JSXElement/spec'; +export type * from './JSXFragment/spec'; +export type * from './literal/spec'; +export type * from './LogicalExpression/spec'; +export type * from './MemberExpression/spec'; +export type * from './MetaProperty/spec'; +export type * from './NewExpression/spec'; +export type * from './ObjectExpression/spec'; +export type * from './SequenceExpression/spec'; +export type * from './Super/spec'; +export type * from './TaggedTemplateExpression/spec'; +export type * from './TemplateLiteral/spec'; +export type * from './ThisExpression/spec'; +export type * from './TSAsExpression/spec'; +export type * from './TSEmptyBodyFunctionExpression/spec'; +export type * from './TSInstantiationExpression/spec'; +export type * from './TSNonNullExpression/spec'; +export type * from './TSSatisfiesExpression/spec'; +export type * from './TSTypeAssertion/spec'; +export type * from './UnaryExpression/spec'; +export type * from './UpdateExpression/spec'; +export type * from './YieldExpression/spec'; diff --git a/packages/ast-spec/src/index.ts b/packages/ast-spec/src/index.ts index f46a8a804b5b..22cc288141ad 100644 --- a/packages/ast-spec/src/index.ts +++ b/packages/ast-spec/src/index.ts @@ -1,49 +1,49 @@ export * from './ast-node-types'; export * from './ast-token-types'; -export * from './base/Accessibility'; -export * from './base/BaseNode'; // this is exported so that the `types` package can merge the decl and add the `parent` property -export * from './base/NodeOrTokenData'; -export * from './base/OptionalRangeAndLoc'; -export * from './base/Position'; -export * from './base/Range'; -export * from './base/SourceLocation'; -export * from './declaration/spec'; -export * from './element/spec'; -export * from './expression/spec'; -export * from './jsx/spec'; -export * from './parameter/spec'; -export * from './special/spec'; -export * from './statement/spec'; -export * from './token/spec'; -export * from './type/spec'; -export * from './unions/BindingName'; -export * from './unions/BindingPattern'; -export * from './unions/CallExpressionArgument'; -export * from './unions/ChainElement'; -export * from './unions/ClassElement'; -export * from './unions/Comment'; -export * from './unions/DeclarationStatement'; -export * from './unions/DestructuringPattern'; -export * from './unions/EntityName'; -export * from './unions/ExportDeclaration'; -export * from './unions/Expression'; -export * from './unions/ForInitialiser'; -export * from './unions/FunctionLike'; -export * from './unions/ImportClause'; -export * from './unions/IterationStatement'; -export * from './unions/JSXChild'; -export * from './unions/JSXExpression'; -export * from './unions/JSXTagNameExpression'; -export * from './unions/LeftHandSideExpression'; -export * from './unions/Literal'; -export * from './unions/LiteralExpression'; -export * from './unions/Node'; -export * from './unions/ObjectLiteralElement'; -export * from './unions/Parameter'; -export * from './unions/PrimaryExpression'; -export * from './unions/PropertyName'; -export * from './unions/Statement'; -export * from './unions/Token'; -export * from './unions/TSUnaryExpression'; -export * from './unions/TypeElement'; -export * from './unions/TypeNode'; +export type * from './base/Accessibility'; +export type * from './base/BaseNode'; // this is exported so that the `types` package can merge the decl and add the `parent` property +export type * from './base/NodeOrTokenData'; +export type * from './base/OptionalRangeAndLoc'; +export type * from './base/Position'; +export type * from './base/Range'; +export type * from './base/SourceLocation'; +export type * from './declaration/spec'; +export type * from './element/spec'; +export type * from './expression/spec'; +export type * from './jsx/spec'; +export type * from './parameter/spec'; +export type * from './special/spec'; +export type * from './statement/spec'; +export type * from './token/spec'; +export type * from './type/spec'; +export type * from './unions/BindingName'; +export type * from './unions/BindingPattern'; +export type * from './unions/CallExpressionArgument'; +export type * from './unions/ChainElement'; +export type * from './unions/ClassElement'; +export type * from './unions/Comment'; +export type * from './unions/DeclarationStatement'; +export type * from './unions/DestructuringPattern'; +export type * from './unions/EntityName'; +export type * from './unions/ExportDeclaration'; +export type * from './unions/Expression'; +export type * from './unions/ForInitialiser'; +export type * from './unions/FunctionLike'; +export type * from './unions/ImportClause'; +export type * from './unions/IterationStatement'; +export type * from './unions/JSXChild'; +export type * from './unions/JSXExpression'; +export type * from './unions/JSXTagNameExpression'; +export type * from './unions/LeftHandSideExpression'; +export type * from './unions/Literal'; +export type * from './unions/LiteralExpression'; +export type * from './unions/Node'; +export type * from './unions/ObjectLiteralElement'; +export type * from './unions/Parameter'; +export type * from './unions/PrimaryExpression'; +export type * from './unions/PropertyName'; +export type * from './unions/Statement'; +export type * from './unions/Token'; +export type * from './unions/TSUnaryExpression'; +export type * from './unions/TypeElement'; +export type * from './unions/TypeNode'; diff --git a/packages/ast-spec/src/jsx/spec.ts b/packages/ast-spec/src/jsx/spec.ts index 1efb134bed7f..51f583d22347 100644 --- a/packages/ast-spec/src/jsx/spec.ts +++ b/packages/ast-spec/src/jsx/spec.ts @@ -1,13 +1,13 @@ -export * from './JSXAttribute/spec'; -export * from './JSXClosingElement/spec'; -export * from './JSXClosingFragment/spec'; -export * from './JSXEmptyExpression/spec'; -export * from './JSXExpressionContainer/spec'; -export * from './JSXIdentifier/spec'; -export * from './JSXMemberExpression/spec'; -export * from './JSXNamespacedName/spec'; -export * from './JSXOpeningElement/spec'; -export * from './JSXOpeningFragment/spec'; -export * from './JSXSpreadAttribute/spec'; -export * from './JSXSpreadChild/spec'; -export * from './JSXText/spec'; +export type * from './JSXAttribute/spec'; +export type * from './JSXClosingElement/spec'; +export type * from './JSXClosingFragment/spec'; +export type * from './JSXEmptyExpression/spec'; +export type * from './JSXExpressionContainer/spec'; +export type * from './JSXIdentifier/spec'; +export type * from './JSXMemberExpression/spec'; +export type * from './JSXNamespacedName/spec'; +export type * from './JSXOpeningElement/spec'; +export type * from './JSXOpeningFragment/spec'; +export type * from './JSXSpreadAttribute/spec'; +export type * from './JSXSpreadChild/spec'; +export type * from './JSXText/spec'; diff --git a/packages/ast-spec/src/parameter/spec.ts b/packages/ast-spec/src/parameter/spec.ts index b006664a36ae..54b1aa1ae048 100644 --- a/packages/ast-spec/src/parameter/spec.ts +++ b/packages/ast-spec/src/parameter/spec.ts @@ -1,5 +1,5 @@ -export * from './ArrayPattern/spec'; -export * from './AssignmentPattern/spec'; -export * from './ObjectPattern/spec'; -export * from './RestElement/spec'; -export * from './TSParameterProperty/spec'; +export type * from './ArrayPattern/spec'; +export type * from './AssignmentPattern/spec'; +export type * from './ObjectPattern/spec'; +export type * from './RestElement/spec'; +export type * from './TSParameterProperty/spec'; diff --git a/packages/ast-spec/src/special/spec.ts b/packages/ast-spec/src/special/spec.ts index 1e7ad26877a9..aff8d8f5975b 100644 --- a/packages/ast-spec/src/special/spec.ts +++ b/packages/ast-spec/src/special/spec.ts @@ -1,24 +1,24 @@ -export * from './CatchClause/spec'; -export * from './ClassBody/spec'; -export * from './Decorator/spec'; -export * from './EmptyStatement/spec'; -export * from './ExportSpecifier/spec'; -export * from './ImportAttribute/spec'; -export * from './ImportDefaultSpecifier/spec'; -export * from './ImportNamespaceSpecifier/spec'; -export * from './ImportSpecifier/spec'; -export * from './PrivateIdentifier/spec'; -export * from './Program/spec'; -export * from './SwitchCase/spec'; -export * from './TemplateElement/spec'; -export * from './TSClassImplements/spec'; -export * from './TSEnumBody/spec'; -export * from './TSExternalModuleReference/spec'; -export * from './TSInterfaceBody/spec'; -export * from './TSInterfaceHeritage/spec'; -export * from './TSModuleBlock/spec'; -export * from './TSTypeAnnotation/spec'; -export * from './TSTypeParameter/spec'; -export * from './TSTypeParameterDeclaration/spec'; -export * from './TSTypeParameterInstantiation/spec'; -export * from './VariableDeclarator/spec'; +export type * from './CatchClause/spec'; +export type * from './ClassBody/spec'; +export type * from './Decorator/spec'; +export type * from './EmptyStatement/spec'; +export type * from './ExportSpecifier/spec'; +export type * from './ImportAttribute/spec'; +export type * from './ImportDefaultSpecifier/spec'; +export type * from './ImportNamespaceSpecifier/spec'; +export type * from './ImportSpecifier/spec'; +export type * from './PrivateIdentifier/spec'; +export type * from './Program/spec'; +export type * from './SwitchCase/spec'; +export type * from './TemplateElement/spec'; +export type * from './TSClassImplements/spec'; +export type * from './TSEnumBody/spec'; +export type * from './TSExternalModuleReference/spec'; +export type * from './TSInterfaceBody/spec'; +export type * from './TSInterfaceHeritage/spec'; +export type * from './TSModuleBlock/spec'; +export type * from './TSTypeAnnotation/spec'; +export type * from './TSTypeParameter/spec'; +export type * from './TSTypeParameterDeclaration/spec'; +export type * from './TSTypeParameterInstantiation/spec'; +export type * from './VariableDeclarator/spec'; diff --git a/packages/ast-spec/src/statement/spec.ts b/packages/ast-spec/src/statement/spec.ts index b581e0a26853..5a961cf137b1 100644 --- a/packages/ast-spec/src/statement/spec.ts +++ b/packages/ast-spec/src/statement/spec.ts @@ -1,18 +1,18 @@ -export * from './BlockStatement/spec'; -export * from './BreakStatement/spec'; -export * from './ContinueStatement/spec'; -export * from './DebuggerStatement/spec'; -export * from './DoWhileStatement/spec'; -export * from './ExpressionStatement/spec'; -export * from './ForInStatement/spec'; -export * from './ForOfStatement/spec'; -export * from './ForStatement/spec'; -export * from './IfStatement/spec'; -export * from './LabeledStatement/spec'; -export * from './ReturnStatement/spec'; -export * from './SwitchStatement/spec'; -export * from './ThrowStatement/spec'; -export * from './TryStatement/spec'; -export * from './TSExportAssignment/spec'; -export * from './WhileStatement/spec'; -export * from './WithStatement/spec'; +export type * from './BlockStatement/spec'; +export type * from './BreakStatement/spec'; +export type * from './ContinueStatement/spec'; +export type * from './DebuggerStatement/spec'; +export type * from './DoWhileStatement/spec'; +export type * from './ExpressionStatement/spec'; +export type * from './ForInStatement/spec'; +export type * from './ForOfStatement/spec'; +export type * from './ForStatement/spec'; +export type * from './IfStatement/spec'; +export type * from './LabeledStatement/spec'; +export type * from './ReturnStatement/spec'; +export type * from './SwitchStatement/spec'; +export type * from './ThrowStatement/spec'; +export type * from './TryStatement/spec'; +export type * from './TSExportAssignment/spec'; +export type * from './WhileStatement/spec'; +export type * from './WithStatement/spec'; diff --git a/packages/ast-spec/src/token/PunctuatorToken/spec.ts b/packages/ast-spec/src/token/PunctuatorToken/spec.ts index 733e0108d2f4..3ce886b69b64 100644 --- a/packages/ast-spec/src/token/PunctuatorToken/spec.ts +++ b/packages/ast-spec/src/token/PunctuatorToken/spec.ts @@ -3,7 +3,7 @@ import type { BaseToken } from '../../base/BaseToken'; import type { ValueOf } from '../../utils'; import type { PunctuatorTokenToText } from './PunctuatorTokenToText'; -export * from './PunctuatorTokenToText'; +export type * from './PunctuatorTokenToText'; export interface PunctuatorToken extends BaseToken { type: AST_TOKEN_TYPES.Punctuator; diff --git a/packages/ast-spec/src/token/spec.ts b/packages/ast-spec/src/token/spec.ts index 3cffaf49e837..91fbeb013601 100644 --- a/packages/ast-spec/src/token/spec.ts +++ b/packages/ast-spec/src/token/spec.ts @@ -1,13 +1,13 @@ -export * from './BlockComment/spec'; -export * from './BooleanToken/spec'; -export * from './IdentifierToken/spec'; -export * from './JSXIdentifierToken/spec'; -export * from './JSXTextToken/spec'; -export * from './KeywordToken/spec'; -export * from './LineComment/spec'; -export * from './NullToken/spec'; -export * from './NumericToken/spec'; -export * from './PunctuatorToken/spec'; -export * from './RegularExpressionToken/spec'; -export * from './StringToken/spec'; -export * from './TemplateToken/spec'; +export type * from './BlockComment/spec'; +export type * from './BooleanToken/spec'; +export type * from './IdentifierToken/spec'; +export type * from './JSXIdentifierToken/spec'; +export type * from './JSXTextToken/spec'; +export type * from './KeywordToken/spec'; +export type * from './LineComment/spec'; +export type * from './NullToken/spec'; +export type * from './NumericToken/spec'; +export type * from './PunctuatorToken/spec'; +export type * from './RegularExpressionToken/spec'; +export type * from './StringToken/spec'; +export type * from './TemplateToken/spec'; diff --git a/packages/ast-spec/src/type/spec.ts b/packages/ast-spec/src/type/spec.ts index ae98ead2dc86..92a78794f4a7 100644 --- a/packages/ast-spec/src/type/spec.ts +++ b/packages/ast-spec/src/type/spec.ts @@ -1,45 +1,45 @@ -export * from './TSAbstractKeyword/spec'; -export * from './TSAnyKeyword/spec'; -export * from './TSArrayType/spec'; -export * from './TSAsyncKeyword/spec'; -export * from './TSBigIntKeyword/spec'; -export * from './TSBooleanKeyword/spec'; -export * from './TSConditionalType/spec'; -export * from './TSConstructorType/spec'; -export * from './TSDeclareKeyword/spec'; -export * from './TSExportKeyword/spec'; -export * from './TSFunctionType/spec'; -export * from './TSImportType/spec'; -export * from './TSIndexedAccessType/spec'; -export * from './TSInferType/spec'; -export * from './TSIntersectionType/spec'; -export * from './TSIntrinsicKeyword/spec'; -export * from './TSLiteralType/spec'; -export * from './TSMappedType/spec'; -export * from './TSNamedTupleMember/spec'; -export * from './TSNeverKeyword/spec'; -export * from './TSNullKeyword/spec'; -export * from './TSNumberKeyword/spec'; -export * from './TSObjectKeyword/spec'; -export * from './TSOptionalType/spec'; -export * from './TSPrivateKeyword/spec'; -export * from './TSProtectedKeyword/spec'; -export * from './TSPublicKeyword/spec'; -export * from './TSQualifiedName/spec'; -export * from './TSReadonlyKeyword/spec'; -export * from './TSRestType/spec'; -export * from './TSStaticKeyword/spec'; -export * from './TSStringKeyword/spec'; -export * from './TSSymbolKeyword/spec'; -export * from './TSTemplateLiteralType/spec'; -export * from './TSThisType/spec'; -export * from './TSTupleType/spec'; -export * from './TSTypeLiteral/spec'; -export * from './TSTypeOperator/spec'; -export * from './TSTypePredicate/spec'; -export * from './TSTypeQuery/spec'; -export * from './TSTypeReference/spec'; -export * from './TSUndefinedKeyword/spec'; -export * from './TSUnionType/spec'; -export * from './TSUnknownKeyword/spec'; -export * from './TSVoidKeyword/spec'; +export type * from './TSAbstractKeyword/spec'; +export type * from './TSAnyKeyword/spec'; +export type * from './TSArrayType/spec'; +export type * from './TSAsyncKeyword/spec'; +export type * from './TSBigIntKeyword/spec'; +export type * from './TSBooleanKeyword/spec'; +export type * from './TSConditionalType/spec'; +export type * from './TSConstructorType/spec'; +export type * from './TSDeclareKeyword/spec'; +export type * from './TSExportKeyword/spec'; +export type * from './TSFunctionType/spec'; +export type * from './TSImportType/spec'; +export type * from './TSIndexedAccessType/spec'; +export type * from './TSInferType/spec'; +export type * from './TSIntersectionType/spec'; +export type * from './TSIntrinsicKeyword/spec'; +export type * from './TSLiteralType/spec'; +export type * from './TSMappedType/spec'; +export type * from './TSNamedTupleMember/spec'; +export type * from './TSNeverKeyword/spec'; +export type * from './TSNullKeyword/spec'; +export type * from './TSNumberKeyword/spec'; +export type * from './TSObjectKeyword/spec'; +export type * from './TSOptionalType/spec'; +export type * from './TSPrivateKeyword/spec'; +export type * from './TSProtectedKeyword/spec'; +export type * from './TSPublicKeyword/spec'; +export type * from './TSQualifiedName/spec'; +export type * from './TSReadonlyKeyword/spec'; +export type * from './TSRestType/spec'; +export type * from './TSStaticKeyword/spec'; +export type * from './TSStringKeyword/spec'; +export type * from './TSSymbolKeyword/spec'; +export type * from './TSTemplateLiteralType/spec'; +export type * from './TSThisType/spec'; +export type * from './TSTupleType/spec'; +export type * from './TSTypeLiteral/spec'; +export type * from './TSTypeOperator/spec'; +export type * from './TSTypePredicate/spec'; +export type * from './TSTypeQuery/spec'; +export type * from './TSTypeReference/spec'; +export type * from './TSUndefinedKeyword/spec'; +export type * from './TSUnionType/spec'; +export type * from './TSUnknownKeyword/spec'; +export type * from './TSVoidKeyword/spec'; diff --git a/packages/eslint-plugin/src/rules/consistent-type-exports.ts b/packages/eslint-plugin/src/rules/consistent-type-exports.ts index 1118c257d00f..dd9d573fcfa9 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-exports.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-exports.ts @@ -1,6 +1,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; -import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import { SymbolFlags } from 'typescript'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; +import * as tsutils from 'ts-api-utils'; +import * as ts from 'typescript'; import { createRule, @@ -77,34 +78,116 @@ export default createRule({ create(context, [{ fixMixedExportsWithInlineTypeSpecifier }]) { const sourceExportsMap: Record = {}; const services = getParserServices(context); + const checker = services.program.getTypeChecker(); /** - * Helper for identifying if an export specifier resolves to a + * Helper for identifying if a symbol resolves to a * JavaScript value or a TypeScript type. * * @returns True/false if is a type or not, or undefined if the specifier * can't be resolved. */ - function isSpecifierTypeBased( - specifier: TSESTree.ExportSpecifier, + function isSymbolTypeBased( + symbol: ts.Symbol | undefined, ): boolean | undefined { - const checker = services.program.getTypeChecker(); - const symbol = services.getSymbolAtLocation(specifier.exported); if (!symbol) { return undefined; } - const aliasedSymbol = checker.getAliasedSymbol(symbol); + const aliasedSymbol = tsutils.isSymbolFlagSet( + symbol, + ts.SymbolFlags.Alias, + ) + ? checker.getAliasedSymbol(symbol) + : symbol; - // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison - if (aliasedSymbol.escapedName === 'unknown') { + if (checker.isUnknownSymbol(aliasedSymbol)) { return undefined; } - return !(aliasedSymbol.flags & SymbolFlags.Value); + return !(aliasedSymbol.flags & ts.SymbolFlags.Value); } return { + ExportAllDeclaration(node): void { + if (node.exportKind === 'type') { + return; + } + + const sourceModule = ts.resolveModuleName( + node.source.value, + context.filename, + services.program.getCompilerOptions(), + ts.sys, + ); + if (sourceModule.resolvedModule == null) { + return; + } + const sourceFile = services.program.getSourceFile( + sourceModule.resolvedModule.resolvedFileName, + ); + if (sourceFile == null) { + return; + } + const sourceFileSymbol = checker.getSymbolAtLocation(sourceFile); + if (sourceFileSymbol == null) { + return; + } + + const sourceFileType = checker.getTypeOfSymbol(sourceFileSymbol); + // Module can explicitly export types or values, and it's not difficult + // to distinguish one from the other, since we can get the flags of + // the exported symbols or check if symbol export declaration has + // the "type" keyword in it. + // + // Things get a lot more complicated when we're dealing with + // export * from './module-with-type-only-exports' + // export type * from './module-with-type-and-value-exports' + // + // TS checker has an internal function getExportsOfModuleWorker that + // recursively visits all module exports, including "export *". It then + // puts type-only-star-exported symbols into the typeOnlyExportStarMap + // property of sourceFile's SymbolLinks. Since symbol links aren't + // exposed outside the checker, we cannot access it directly. + // + // Therefore, to filter out value properties, we use the following hack: + // checker.getPropertiesOfType returns all exports that were originally + // values, but checker.getPropertyOfType returns undefined for + // properties that are mentioned in the typeOnlyExportStarMap. + const isThereAnyExportedValue = checker + .getPropertiesOfType(sourceFileType) + .some( + propertyTypeSymbol => + checker.getPropertyOfType( + sourceFileType, + propertyTypeSymbol.escapedName.toString(), + ) != null, + ); + if (isThereAnyExportedValue) { + return; + } + + context.report({ + node, + messageId: 'typeOverValue', + fix(fixer) { + const asteriskToken = nullThrows( + context.sourceCode.getFirstToken( + node, + token => + token.type === AST_TOKEN_TYPES.Punctuator && + token.value === '*', + ), + NullThrowsReasons.MissingToken( + 'asterisk', + 'export all declaration', + ), + ); + + return fixer.insertTextBefore(asteriskToken, 'type '); + }, + }); + }, ExportNamedDeclaration(node: TSESTree.ExportNamedDeclaration): void { // Coerce the source into a string for use as a lookup entry. const source = getSourceFromExport(node) ?? 'undefined'; @@ -142,7 +225,9 @@ export default createRule({ continue; } - const isTypeBased = isSpecifierTypeBased(specifier); + const isTypeBased = isSymbolTypeBased( + services.getSymbolAtLocation(specifier.exported), + ); if (isTypeBased === true) { typeBasedSpecifiers.push(specifier); diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index b13b5855231d..95f756d6822d 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -18,7 +18,7 @@ export * from './misc'; export * from './needsPrecedingSemiColon'; export * from './objectIterators'; export * from './scopeUtils'; -export * from './types'; +export type * from './types'; export * from './isAssignee'; export * from './getFixOrSuggest'; export * from './isArrayMethodCallWithPredicate'; diff --git a/packages/eslint-plugin/tests/fixtures/consistent-type-exports.ts b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/index.ts similarity index 80% rename from packages/eslint-plugin/tests/fixtures/consistent-type-exports.ts rename to packages/eslint-plugin/tests/fixtures/consistent-type-exports/index.ts index 0c883cbef773..cd6777fd1b68 100644 --- a/packages/eslint-plugin/tests/fixtures/consistent-type-exports.ts +++ b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/index.ts @@ -2,3 +2,5 @@ export type Type1 = 1; export type Type2 = 1; export const value1 = 2; export const value2 = 2; + +export class Class1 {} diff --git a/packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-exports.ts b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-exports.ts new file mode 100644 index 000000000000..bc999f468ed7 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-exports.ts @@ -0,0 +1,9 @@ +export type TypeFoo = 1; + +export interface InterfaceFoo { + foo: 'bar'; +} + +class LocalClass {} + +export type { LocalClass }; diff --git a/packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-reexport.ts b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-reexport.ts new file mode 100644 index 000000000000..5f5eb0ba0c30 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/type-only-reexport.ts @@ -0,0 +1,13 @@ +export * from './type-only-exports'; + +export type * as typeOnlyExports from './type-only-exports'; + +export type * from './index'; + +export type * as indexExports from './index'; + +export { Type1 as AliasedType1 } from './index'; + +import { Class1 } from './index'; + +export { type Class1 as AliasedClass1 }; diff --git a/packages/eslint-plugin/tests/fixtures/consistent-type-exports/value-reexport.ts b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/value-reexport.ts new file mode 100644 index 000000000000..ea465c2a34a4 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/consistent-type-exports/value-reexport.ts @@ -0,0 +1 @@ +export * from './index'; diff --git a/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts b/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts index 209a285c7ee1..8ccfeb9bf5d5 100644 --- a/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts +++ b/packages/eslint-plugin/tests/rules/consistent-type-exports.test.ts @@ -53,6 +53,15 @@ namespace NonTypeNS { export { NonTypeNS }; `, + "export * from './unknown-module';", + "export * from './consistent-type-exports';", + "export type * from './consistent-type-exports/type-only-exports';", + "export type * from './consistent-type-exports/type-only-reexport';", + "export * from './consistent-type-exports/value-reexport';", + "export * as foo from './consistent-type-exports';", + "export type * as foo from './consistent-type-exports/type-only-exports';", + "export type * as foo from './consistent-type-exports/type-only-reexport';", + "export * as foo from './consistent-type-exports/value-reexport';", ], invalid: [ { @@ -386,5 +395,79 @@ export { }, ], }, + { + code: ` + export * from './consistent-type-exports/type-only-exports'; + `, + output: ` + export type * from './consistent-type-exports/type-only-exports'; + `, + errors: [ + { + column: 9, + endColumn: 69, + line: 2, + endLine: 2, + messageId: 'typeOverValue', + }, + ], + }, + { + code: noFormat` + /* comment 1 */ export + /* comment 2 */ * + // comment 3 + from './consistent-type-exports/type-only-exports'; + `, + output: ` + /* comment 1 */ export + /* comment 2 */ type * + // comment 3 + from './consistent-type-exports/type-only-exports'; + `, + errors: [ + { + column: 25, + endColumn: 64, + line: 2, + endLine: 5, + messageId: 'typeOverValue', + }, + ], + }, + { + code: ` + export * from './consistent-type-exports/type-only-reexport'; + `, + output: ` + export type * from './consistent-type-exports/type-only-reexport'; + `, + errors: [ + { + column: 9, + endColumn: 70, + line: 2, + endLine: 2, + messageId: 'typeOverValue', + }, + ], + }, + { + code: ` + export * as foo from './consistent-type-exports/type-only-reexport'; + `, + output: ` + export type * as foo from './consistent-type-exports/type-only-reexport'; + `, + errors: [ + { + column: 9, + endColumn: 77, + line: 2, + endLine: 2, + messageId: 'typeOverValue', + }, + ], + }, ], }); diff --git a/packages/scope-manager/src/definition/index.ts b/packages/scope-manager/src/definition/index.ts index 9bd1f0123370..d85a0d1c1e08 100644 --- a/packages/scope-manager/src/definition/index.ts +++ b/packages/scope-manager/src/definition/index.ts @@ -1,6 +1,6 @@ export * from './CatchClauseDefinition'; export * from './ClassNameDefinition'; -export * from './Definition'; +export type * from './Definition'; export * from './DefinitionType'; export * from './FunctionNameDefinition'; export * from './ImplicitGlobalVariableDefinition'; diff --git a/packages/scope-manager/src/scope/index.ts b/packages/scope-manager/src/scope/index.ts index c94cc2982e1f..f1f31ced44c4 100644 --- a/packages/scope-manager/src/scope/index.ts +++ b/packages/scope-manager/src/scope/index.ts @@ -10,7 +10,7 @@ export * from './FunctionTypeScope'; export * from './GlobalScope'; export * from './MappedTypeScope'; export * from './ModuleScope'; -export * from './Scope'; +export type * from './Scope'; export * from './ScopeType'; export * from './SwitchScope'; export * from './TSEnumScope'; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index bb733d5275aa..4429294e77e2 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -1,4 +1,4 @@ export { AST_NODE_TYPES, AST_TOKEN_TYPES } from './generated/ast-spec'; -export * from './lib'; -export * from './parser-options'; +export type * from './lib'; +export type * from './parser-options'; export * from './ts-estree'; diff --git a/packages/typescript-estree/src/ts-estree/index.ts b/packages/typescript-estree/src/ts-estree/index.ts index a7f64d91ce6b..f80c711bc8be 100644 --- a/packages/typescript-estree/src/ts-estree/index.ts +++ b/packages/typescript-estree/src/ts-estree/index.ts @@ -4,5 +4,5 @@ export { AST_TOKEN_TYPES, TSESTree, } from '@typescript-eslint/types'; -export * from './ts-nodes'; -export * from './estree-to-ts-node-types'; +export type * from './ts-nodes'; +export type * from './estree-to-ts-node-types'; diff --git a/packages/utils/src/eslint-utils/index.ts b/packages/utils/src/eslint-utils/index.ts index 632b6e051981..e8a5401bbc09 100644 --- a/packages/utils/src/eslint-utils/index.ts +++ b/packages/utils/src/eslint-utils/index.ts @@ -1,6 +1,6 @@ export * from './applyDefault'; export * from './deepMerge'; export * from './getParserServices'; -export * from './InferTypesFromRule'; +export type * from './InferTypesFromRule'; export * from './nullThrows'; export * from './RuleCreator'; diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index d6d907cd0ef4..42bc96dbcbbe 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -1,7 +1,7 @@ export * as ASTUtils from './ast-utils'; export * as ESLintUtils from './eslint-utils'; -export * as JSONSchema from './json-schema'; +export type * as JSONSchema from './json-schema'; export * as TSESLint from './ts-eslint'; export * from './ts-estree'; export * as TSUtils from './ts-utils'; diff --git a/packages/utils/src/ts-eslint/index.ts b/packages/utils/src/ts-eslint/index.ts index 217b46dcbf7d..d2e69666be1e 100644 --- a/packages/utils/src/ts-eslint/index.ts +++ b/packages/utils/src/ts-eslint/index.ts @@ -1,11 +1,11 @@ -export * from './AST'; -export * from './Config'; +export type * from './AST'; +export type * from './Config'; export * from './ESLint'; export * from './Linter'; -export * from './Parser'; -export * from './ParserOptions'; -export * from './Processor'; -export * from './Rule'; +export type * from './Parser'; +export type * from './ParserOptions'; +export type * from './Processor'; +export type * from './Rule'; export * from './RuleTester'; export * from './Scope'; export * from './SourceCode'; From cdd93f66d445d79bc061a6e03d312622f1dae29e Mon Sep 17 00:00:00 2001 From: Yuya Yoshioka <71418423+YuyaYoshioka@users.noreply.github.com> Date: Tue, 24 Sep 2024 01:39:15 +0900 Subject: [PATCH 15/16] docs(eslint-plugin): [no-inferrable-types] do not use no-inferrable-types with isolatedDeclarations compiler option (#9956) docs: do not use no-inferrable-types with isolatedDeclarations compiler option --- packages/eslint-plugin/docs/rules/no-inferrable-types.mdx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/no-inferrable-types.mdx b/packages/eslint-plugin/docs/rules/no-inferrable-types.mdx index 0cf7caa4d767..1bddfeb3c249 100644 --- a/packages/eslint-plugin/docs/rules/no-inferrable-types.mdx +++ b/packages/eslint-plugin/docs/rules/no-inferrable-types.mdx @@ -106,6 +106,8 @@ class Foo { If you strongly prefer to have explicit types regardless of whether they can be inferred, this rule may not be for you. +If you use the `--isolatedDeclarations` compiler option, this rule is incompatible. + ## Further Reading - [TypeScript Inference](https://www.typescriptlang.org/docs/handbook/type-inference.html) From b88ea33f34e0b5f6fc5bd3463a5b32a5c9df8b7e Mon Sep 17 00:00:00 2001 From: "typescript-eslint[bot]" Date: Mon, 23 Sep 2024 17:17:34 +0000 Subject: [PATCH 16/16] chore(release): publish 8.7.0 --- CHANGELOG.md | 25 ++++++ packages/ast-spec/CHANGELOG.md | 17 ++++ packages/ast-spec/package.json | 2 +- packages/eslint-plugin/CHANGELOG.md | 28 +++++++ packages/eslint-plugin/package.json | 14 ++-- packages/parser/CHANGELOG.md | 6 ++ packages/parser/package.json | 10 +-- .../CHANGELOG.md | 6 ++ .../package.json | 6 +- packages/rule-tester/CHANGELOG.md | 6 ++ packages/rule-tester/package.json | 8 +- packages/scope-manager/CHANGELOG.md | 17 ++++ packages/scope-manager/package.json | 8 +- packages/type-utils/CHANGELOG.md | 6 ++ packages/type-utils/package.json | 8 +- packages/types/CHANGELOG.md | 22 +++++ packages/types/package.json | 2 +- packages/typescript-eslint/CHANGELOG.md | 6 ++ packages/typescript-eslint/package.json | 8 +- packages/typescript-estree/CHANGELOG.md | 17 ++++ packages/typescript-estree/package.json | 6 +- packages/utils/CHANGELOG.md | 22 +++++ packages/utils/package.json | 8 +- packages/visitor-keys/CHANGELOG.md | 6 ++ packages/visitor-keys/package.json | 4 +- yarn.lock | 80 +++++++++---------- 26 files changed, 266 insertions(+), 82 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce3126e8adb4..13b1ffdfcf6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,28 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [no-unsafe-call] check calls of Function ([#10010](https://github.com/typescript-eslint/typescript-eslint/pull/10010)) +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types ([#10006](https://github.com/typescript-eslint/typescript-eslint/pull/10006)) + +### 🩹 Fixes + +- **eslint-plugin:** properly coerce all types to string in `getStaticMemberAccessValue` ([#10004](https://github.com/typescript-eslint/typescript-eslint/pull/10004)) +- **eslint-plugin:** [no-deprecated] report on imported deprecated variables ([#9987](https://github.com/typescript-eslint/typescript-eslint/pull/9987)) +- **eslint-plugin:** [no-confusing-non-null-assertion] check !in and !instanceof ([#9994](https://github.com/typescript-eslint/typescript-eslint/pull/9994)) +- **types:** add `NewExpression` as a parent of `SpreadElement` ([#10024](https://github.com/typescript-eslint/typescript-eslint/pull/10024)) +- **utils:** add missing entries to the RuleListener selectors list ([#9992](https://github.com/typescript-eslint/typescript-eslint/pull/9992)) + +### ❤️ Thank You + +- Abraham Guo +- auvred @auvred +- Brian Donovan @eventualbuddha +- Kirk Waiblinger @kirkwaiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index cc6fbe1a3982..a913f9901fd2 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,20 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Brian Donovan +- Kirk Waiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for ast-spec to align it with other projects, there were no code changes. diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index ca0618bdb437..12778f959474 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "8.6.0", + "version": "8.7.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 9e4a005bbfe1..2cf93b418bb9 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,31 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [no-unsafe-call] check calls of Function + +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types + + +### 🩹 Fixes + +- **eslint-plugin:** properly coerce all types to string in `getStaticMemberAccessValue` + +- **eslint-plugin:** [no-deprecated] report on imported deprecated variables + +- **eslint-plugin:** [no-confusing-non-null-assertion] check !in and !instanceof + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Brian Donovan +- Kirk Waiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 424409b491db..8ff9322985a9 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "8.6.0", + "version": "8.7.0", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -60,10 +60,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.6.0", - "@typescript-eslint/type-utils": "8.6.0", - "@typescript-eslint/utils": "8.6.0", - "@typescript-eslint/visitor-keys": "8.6.0", + "@typescript-eslint/scope-manager": "8.7.0", + "@typescript-eslint/type-utils": "8.7.0", + "@typescript-eslint/utils": "8.7.0", + "@typescript-eslint/visitor-keys": "8.7.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -74,8 +74,8 @@ "@types/marked": "^5.0.2", "@types/mdast": "^4.0.3", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "8.6.0", - "@typescript-eslint/rule-tester": "8.6.0", + "@typescript-eslint/rule-schema-to-typescript-types": "8.7.0", + "@typescript-eslint/rule-tester": "8.7.0", "ajv": "^6.12.6", "cross-env": "^7.0.3", "cross-fetch": "*", diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index e4e3b58b7a44..7fc1c81d900a 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.7.0 (2024-09-23) + +This was a version bump only for parser to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for parser to align it with other projects, there were no code changes. diff --git a/packages/parser/package.json b/packages/parser/package.json index 57c7d2decba8..424e46f1a7fa 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "8.6.0", + "version": "8.7.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -52,10 +52,10 @@ "eslint": "^8.57.0 || ^9.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "8.6.0", - "@typescript-eslint/types": "8.6.0", - "@typescript-eslint/typescript-estree": "8.6.0", - "@typescript-eslint/visitor-keys": "8.6.0", + "@typescript-eslint/scope-manager": "8.7.0", + "@typescript-eslint/types": "8.7.0", + "@typescript-eslint/typescript-estree": "8.7.0", + "@typescript-eslint/visitor-keys": "8.7.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index ea8491e34be7..c18ca1c9fb38 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.7.0 (2024-09-23) + +This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for rule-schema-to-typescript-types to align it with other projects, there were no code changes. diff --git a/packages/rule-schema-to-typescript-types/package.json b/packages/rule-schema-to-typescript-types/package.json index 508958d87e2f..6628061692a8 100644 --- a/packages/rule-schema-to-typescript-types/package.json +++ b/packages/rule-schema-to-typescript-types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-schema-to-typescript-types", - "version": "8.6.0", + "version": "8.7.0", "private": true, "type": "commonjs", "exports": { @@ -34,8 +34,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/type-utils": "8.6.0", - "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/type-utils": "8.7.0", + "@typescript-eslint/utils": "8.7.0", "natural-compare": "^1.4.0", "prettier": "^3.2.5" }, diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 80196faf86cf..3d1361dd36b0 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.7.0 (2024-09-23) + +This was a version bump only for rule-tester to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for rule-tester to align it with other projects, there were no code changes. diff --git a/packages/rule-tester/package.json b/packages/rule-tester/package.json index e22d75355525..cb25f8b828bb 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "8.6.0", + "version": "8.7.0", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -48,8 +48,8 @@ }, "//": "NOTE - AJV is out-of-date, but it's intentionally synced with ESLint - https://github.com/eslint/eslint/blob/ad9dd6a933fd098a0d99c6a9aa059850535c23ee/package.json#L70", "dependencies": { - "@typescript-eslint/typescript-estree": "8.6.0", - "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/typescript-estree": "8.7.0", + "@typescript-eslint/utils": "8.7.0", "ajv": "^6.12.6", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "4.6.2", @@ -62,7 +62,7 @@ "@jest/types": "29.6.3", "@types/json-stable-stringify-without-jsonify": "^1.0.2", "@types/lodash.merge": "4.6.9", - "@typescript-eslint/parser": "8.6.0", + "@typescript-eslint/parser": "8.7.0", "chai": "^4.4.1", "eslint-visitor-keys": "^4.0.0", "espree": "^10.0.1", diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index ed90bc47b140..4c23dce5b0b1 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,20 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Brian Donovan +- Kirk Waiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for scope-manager to align it with other projects, there were no code changes. diff --git a/packages/scope-manager/package.json b/packages/scope-manager/package.json index 0369a15611f1..59c358d9b578 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "8.6.0", + "version": "8.7.0", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -46,13 +46,13 @@ "typecheck": "npx nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "8.6.0", - "@typescript-eslint/visitor-keys": "8.6.0" + "@typescript-eslint/types": "8.7.0", + "@typescript-eslint/visitor-keys": "8.7.0" }, "devDependencies": { "@jest/types": "29.6.3", "@types/glob": "*", - "@typescript-eslint/typescript-estree": "8.6.0", + "@typescript-eslint/typescript-estree": "8.7.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index d0b9fc22e94f..0337fc6d6708 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.7.0 (2024-09-23) + +This was a version bump only for type-utils to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index b630c55ed655..df2f0ec420e9 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "8.6.0", + "version": "8.7.0", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -46,14 +46,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "8.6.0", - "@typescript-eslint/utils": "8.6.0", + "@typescript-eslint/typescript-estree": "8.7.0", + "@typescript-eslint/utils": "8.7.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "devDependencies": { "@jest/types": "29.6.3", - "@typescript-eslint/parser": "8.6.0", + "@typescript-eslint/parser": "8.7.0", "ajv": "^6.12.6", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index b4976ba45fa2..2d011ea3672e 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,25 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types + + +### 🩹 Fixes + +- **types:** add `NewExpression` as a parent of `SpreadElement` + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Brian Donovan +- Kirk Waiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) diff --git a/packages/types/package.json b/packages/types/package.json index 786b7b8afd4d..8f84805ea191 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "8.6.0", + "version": "8.7.0", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", diff --git a/packages/typescript-eslint/CHANGELOG.md b/packages/typescript-eslint/CHANGELOG.md index 946c37edb01b..b317550bca1f 100644 --- a/packages/typescript-eslint/CHANGELOG.md +++ b/packages/typescript-eslint/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.7.0 (2024-09-23) + +This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for typescript-eslint to align it with other projects, there were no code changes. diff --git a/packages/typescript-eslint/package.json b/packages/typescript-eslint/package.json index 9628438305e3..61f52f24e2da 100644 --- a/packages/typescript-eslint/package.json +++ b/packages/typescript-eslint/package.json @@ -1,6 +1,6 @@ { "name": "typescript-eslint", - "version": "8.6.0", + "version": "8.7.0", "description": "Tooling which enables you to use TypeScript with ESLint", "files": [ "dist", @@ -52,9 +52,9 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/eslint-plugin": "8.6.0", - "@typescript-eslint/parser": "8.6.0", - "@typescript-eslint/utils": "8.6.0" + "@typescript-eslint/eslint-plugin": "8.7.0", + "@typescript-eslint/parser": "8.7.0", + "@typescript-eslint/utils": "8.7.0" }, "devDependencies": { "@jest/types": "29.6.3", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 449cd2176f73..3f35f359ce53 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,20 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Brian Donovan +- Kirk Waiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 7344425bbc58..325a74003e9a 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "8.6.0", + "version": "8.7.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -54,8 +54,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.6.0", - "@typescript-eslint/visitor-keys": "8.6.0", + "@typescript-eslint/types": "8.7.0", + "@typescript-eslint/visitor-keys": "8.7.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 01c5c0820c32..ba88fd48c4eb 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,25 @@ +## 8.7.0 (2024-09-23) + + +### 🚀 Features + +- **eslint-plugin:** [consistent-type-exports] check `export *` exports to see if all exported members are types + + +### 🩹 Fixes + +- **utils:** add missing entries to the RuleListener selectors list + + +### ❤️ Thank You + +- Abraham Guo +- auvred +- Brian Donovan +- Kirk Waiblinger + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) diff --git a/packages/utils/package.json b/packages/utils/package.json index 10bd4aec62ba..d801d5323121 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "8.6.0", + "version": "8.7.0", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -64,9 +64,9 @@ }, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.6.0", - "@typescript-eslint/types": "8.6.0", - "@typescript-eslint/typescript-estree": "8.6.0" + "@typescript-eslint/scope-manager": "8.7.0", + "@typescript-eslint/types": "8.7.0", + "@typescript-eslint/typescript-estree": "8.7.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0" diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 74b233c65975..4b258c7919b7 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,9 @@ +## 8.7.0 (2024-09-23) + +This was a version bump only for visitor-keys to align it with other projects, there were no code changes. + +You can read about our [versioning strategy](https://main--typescript-eslint.netlify.app/users/versioning) and [releases](https://main--typescript-eslint.netlify.app/users/releases) on our website. + ## 8.6.0 (2024-09-16) This was a version bump only for visitor-keys to align it with other projects, there were no code changes. diff --git a/packages/visitor-keys/package.json b/packages/visitor-keys/package.json index 0fda5d287905..e4b6d44687d1 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "8.6.0", + "version": "8.7.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -47,7 +47,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "8.6.0", + "@typescript-eslint/types": "8.7.0", "eslint-visitor-keys": "^3.4.3" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index cf3a848cc305..d607f912b0b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5638,7 +5638,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/eslint-plugin@8.6.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": +"@typescript-eslint/eslint-plugin@8.7.0, @typescript-eslint/eslint-plugin@workspace:*, @typescript-eslint/eslint-plugin@workspace:^, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -5647,12 +5647,12 @@ __metadata: "@types/marked": ^5.0.2 "@types/mdast": ^4.0.3 "@types/natural-compare": "*" - "@typescript-eslint/rule-schema-to-typescript-types": 8.6.0 - "@typescript-eslint/rule-tester": 8.6.0 - "@typescript-eslint/scope-manager": 8.6.0 - "@typescript-eslint/type-utils": 8.6.0 - "@typescript-eslint/utils": 8.6.0 - "@typescript-eslint/visitor-keys": 8.6.0 + "@typescript-eslint/rule-schema-to-typescript-types": 8.7.0 + "@typescript-eslint/rule-tester": 8.7.0 + "@typescript-eslint/scope-manager": 8.7.0 + "@typescript-eslint/type-utils": 8.7.0 + "@typescript-eslint/utils": 8.7.0 + "@typescript-eslint/visitor-keys": 8.7.0 ajv: ^6.12.6 cross-env: ^7.0.3 cross-fetch: "*" @@ -5696,16 +5696,16 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/parser@8.6.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": +"@typescript-eslint/parser@8.7.0, @typescript-eslint/parser@workspace:*, @typescript-eslint/parser@workspace:packages/parser": version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/scope-manager": 8.6.0 - "@typescript-eslint/types": 8.6.0 - "@typescript-eslint/typescript-estree": 8.6.0 - "@typescript-eslint/visitor-keys": 8.6.0 + "@typescript-eslint/scope-manager": 8.7.0 + "@typescript-eslint/types": 8.7.0 + "@typescript-eslint/typescript-estree": 8.7.0 + "@typescript-eslint/visitor-keys": 8.7.0 debug: ^4.3.4 downlevel-dts: "*" glob: "*" @@ -5721,28 +5721,28 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-schema-to-typescript-types@8.6.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": +"@typescript-eslint/rule-schema-to-typescript-types@8.7.0, @typescript-eslint/rule-schema-to-typescript-types@workspace:*, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/type-utils": 8.6.0 - "@typescript-eslint/utils": 8.6.0 + "@typescript-eslint/type-utils": 8.7.0 + "@typescript-eslint/utils": 8.7.0 natural-compare: ^1.4.0 prettier: ^3.2.5 languageName: unknown linkType: soft -"@typescript-eslint/rule-tester@8.6.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": +"@typescript-eslint/rule-tester@8.7.0, @typescript-eslint/rule-tester@workspace:*, @typescript-eslint/rule-tester@workspace:packages/rule-tester": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-tester@workspace:packages/rule-tester" dependencies: "@jest/types": 29.6.3 "@types/json-stable-stringify-without-jsonify": ^1.0.2 "@types/lodash.merge": 4.6.9 - "@typescript-eslint/parser": 8.6.0 - "@typescript-eslint/typescript-estree": 8.6.0 - "@typescript-eslint/utils": 8.6.0 + "@typescript-eslint/parser": 8.7.0 + "@typescript-eslint/typescript-estree": 8.7.0 + "@typescript-eslint/utils": 8.7.0 ajv: ^6.12.6 chai: ^4.4.1 eslint-visitor-keys: ^4.0.0 @@ -5760,15 +5760,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/scope-manager@8.6.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": +"@typescript-eslint/scope-manager@8.7.0, @typescript-eslint/scope-manager@workspace:*, @typescript-eslint/scope-manager@workspace:^, @typescript-eslint/scope-manager@workspace:packages/scope-manager": version: 0.0.0-use.local resolution: "@typescript-eslint/scope-manager@workspace:packages/scope-manager" dependencies: "@jest/types": 29.6.3 "@types/glob": "*" - "@typescript-eslint/types": 8.6.0 - "@typescript-eslint/typescript-estree": 8.6.0 - "@typescript-eslint/visitor-keys": 8.6.0 + "@typescript-eslint/types": 8.7.0 + "@typescript-eslint/typescript-estree": 8.7.0 + "@typescript-eslint/visitor-keys": 8.7.0 glob: "*" jest-specific-snapshot: "*" make-dir: "*" @@ -5787,14 +5787,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@8.6.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": +"@typescript-eslint/type-utils@8.7.0, @typescript-eslint/type-utils@workspace:*, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/parser": 8.6.0 - "@typescript-eslint/typescript-estree": 8.6.0 - "@typescript-eslint/utils": 8.6.0 + "@typescript-eslint/parser": 8.7.0 + "@typescript-eslint/typescript-estree": 8.7.0 + "@typescript-eslint/utils": 8.7.0 ajv: ^6.12.6 debug: ^4.3.4 downlevel-dts: "*" @@ -5809,7 +5809,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@8.6.0, @typescript-eslint/types@^8.3.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@8.7.0, @typescript-eslint/types@^8.3.0, @typescript-eslint/types@workspace:*, @typescript-eslint/types@workspace:^, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -5910,13 +5910,13 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/typescript-estree@8.6.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": +"@typescript-eslint/typescript-estree@8.7.0, @typescript-eslint/typescript-estree@workspace:*, @typescript-eslint/typescript-estree@workspace:^, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/types": 8.6.0 - "@typescript-eslint/visitor-keys": 8.6.0 + "@typescript-eslint/types": 8.7.0 + "@typescript-eslint/visitor-keys": 8.7.0 debug: ^4.3.4 fast-glob: ^3.3.2 glob: "*" @@ -5953,14 +5953,14 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@8.6.0, @typescript-eslint/utils@^8.3.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": +"@typescript-eslint/utils@8.7.0, @typescript-eslint/utils@^8.3.0, @typescript-eslint/utils@workspace:*, @typescript-eslint/utils@workspace:^, @typescript-eslint/utils@workspace:packages/utils": version: 0.0.0-use.local resolution: "@typescript-eslint/utils@workspace:packages/utils" dependencies: "@eslint-community/eslint-utils": ^4.4.0 - "@typescript-eslint/scope-manager": 8.6.0 - "@typescript-eslint/types": 8.6.0 - "@typescript-eslint/typescript-estree": 8.6.0 + "@typescript-eslint/scope-manager": 8.7.0 + "@typescript-eslint/types": 8.7.0 + "@typescript-eslint/typescript-estree": 8.7.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5 @@ -5989,13 +5989,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@8.6.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": +"@typescript-eslint/visitor-keys@8.7.0, @typescript-eslint/visitor-keys@workspace:*, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": version: 0.0.0-use.local resolution: "@typescript-eslint/visitor-keys@workspace:packages/visitor-keys" dependencies: "@jest/types": 29.6.3 "@types/eslint-visitor-keys": "*" - "@typescript-eslint/types": 8.6.0 + "@typescript-eslint/types": 8.7.0 downlevel-dts: "*" eslint-visitor-keys: ^3.4.3 jest: 29.7.0 @@ -19543,9 +19543,9 @@ __metadata: resolution: "typescript-eslint@workspace:packages/typescript-eslint" dependencies: "@jest/types": 29.6.3 - "@typescript-eslint/eslint-plugin": 8.6.0 - "@typescript-eslint/parser": 8.6.0 - "@typescript-eslint/utils": 8.6.0 + "@typescript-eslint/eslint-plugin": 8.7.0 + "@typescript-eslint/parser": 8.7.0 + "@typescript-eslint/utils": 8.7.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.2.5