From 1aa8664bab1f7ea77539ec83f65123e8ebe34b39 Mon Sep 17 00:00:00 2001 From: auvred <61150013+auvred@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:46:22 +0300 Subject: [PATCH 01/13] feat(eslint-plugin): [prefer-promise-reject-errors] add rule (#8011) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(eslint-plugin): [prefer-promise-reject-errors] new rule! * test: ~100% coverage * docs: add rule docs * test: add some cases * chore: lint --fix * chore: reformat tests * feat: add support for literal computed reject name * chore: lint --fix * refactor: get rid of one @ts-expect-error * docs: refer to the original rule description * test: add few cases * docs: remove some examples * refactor: move check if symbol is from default lib or not to new fn * refactor: assert that rejectVariable is non-nullable * chore: remove assertion in skipChainExpression * test: specify error ranges for invalid test cases * chore: format tests * chore: remove unused check if variable reference is read or not * chore: include rule to `strict-type-checked` config * refactor: simplify isSymbolFromDefaultLibrary * chore: remove ts-expect-error comment * feat: add checks for Promise child classes and unions/intersections * Update packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md Co-authored-by: Josh Goldberg ✨ * refactor: `program` -> `services.program` * refactor: split unreadable if condition * docs: simplify examples * refactor: rename `isBuiltinSymbolLike.ts` -> `builtinSymbolLikes.ts` * perf: get type of `reject` callee lazily * test: add cases with arrays,never,unknown * feat: add support for `Readonly` and similar * chore: fix lint issues --------- Co-authored-by: Josh Goldberg ✨ --- .../rules/prefer-promise-reject-errors.md | 50 + packages/eslint-plugin/src/configs/all.ts | 2 + .../src/configs/disable-type-checked.ts | 1 + .../src/configs/strict-type-checked.ts | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-throw-literal.ts | 38 +- .../src/rules/prefer-promise-reject-errors.ts | 153 ++ .../prefer-promise-reject-errors.test.ts | 1438 +++++++++++++++++ .../prefer-promise-reject-errors.shot | 28 + packages/type-utils/src/builtinSymbolLikes.ts | 161 ++ packages/type-utils/src/index.ts | 2 + .../src/isSymbolFromDefaultLibrary.ts | 20 + 12 files changed, 1861 insertions(+), 36 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md create mode 100644 packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts create mode 100644 packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/prefer-promise-reject-errors.shot create mode 100644 packages/type-utils/src/builtinSymbolLikes.ts create mode 100644 packages/type-utils/src/isSymbolFromDefaultLibrary.ts diff --git a/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md b/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md new file mode 100644 index 000000000000..28e465cf00f1 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-promise-reject-errors.md @@ -0,0 +1,50 @@ +--- +description: 'Require using Error objects as Promise rejection reasons.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/prefer-promise-reject-errors** for documentation. + +This rule extends the base [`eslint/prefer-promise-reject-errors`](https://eslint.org/docs/rules/prefer-promise-reject-errors) rule. +It uses type information to enforce that `Promise`s are only rejected with `Error` objects. + +## Examples + + + +### ❌ Incorrect + +```ts +Promise.reject('error'); + +const err = new Error(); +Promise.reject('an ' + err); + +new Promise((resolve, reject) => reject('error')); + +new Promise((resolve, reject) => { + const err = new Error(); + reject('an ' + err); +}); +``` + +### ✅ Correct + +```ts +Promise.reject(new Error()); + +class CustomError extends Error { + // ... +} +Promise.reject(new CustomError()); + +new Promise((resolve, reject) => reject(new Error())); + +new Promise((resolve, reject) => { + class CustomError extends Error { + // ... + } + return reject(new CustomError()); +}); +``` diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 9881e3397de2..31bf124f451c 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -125,6 +125,8 @@ export = { '@typescript-eslint/prefer-namespace-keyword': 'error', '@typescript-eslint/prefer-nullish-coalescing': 'error', '@typescript-eslint/prefer-optional-chain': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-readonly': 'error', '@typescript-eslint/prefer-readonly-parameter-types': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 2fe413146c7b..073c8b10c8a9 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -41,6 +41,7 @@ export = { '@typescript-eslint/prefer-includes': 'off', '@typescript-eslint/prefer-nullish-coalescing': 'off', '@typescript-eslint/prefer-optional-chain': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', '@typescript-eslint/prefer-readonly': 'off', '@typescript-eslint/prefer-readonly-parameter-types': 'off', '@typescript-eslint/prefer-reduce-type-parameter': 'off', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 471175b9bba7..8bf73cbe7ee6 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -61,6 +61,8 @@ export = { '@typescript-eslint/prefer-as-const': 'error', '@typescript-eslint/prefer-includes': 'error', '@typescript-eslint/prefer-literal-enum-member': 'error', + 'prefer-promise-reject-errors': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'error', '@typescript-eslint/prefer-reduce-type-parameter': 'error', '@typescript-eslint/prefer-return-this-type': 'error', '@typescript-eslint/prefer-ts-expect-error': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 14c171af990e..d162be286e8e 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -109,6 +109,7 @@ import preferLiteralEnumMember from './prefer-literal-enum-member'; import preferNamespaceKeyword from './prefer-namespace-keyword'; import preferNullishCoalescing from './prefer-nullish-coalescing'; import preferOptionalChain from './prefer-optional-chain'; +import preferPromiseRejectErrors from './prefer-promise-reject-errors'; import preferReadonly from './prefer-readonly'; import preferReadonlyParameterTypes from './prefer-readonly-parameter-types'; import preferReduceTypeParameter from './prefer-reduce-type-parameter'; @@ -248,6 +249,7 @@ export default { 'prefer-namespace-keyword': preferNamespaceKeyword, 'prefer-nullish-coalescing': preferNullishCoalescing, 'prefer-optional-chain': preferOptionalChain, + 'prefer-promise-reject-errors': preferPromiseRejectErrors, 'prefer-readonly': preferReadonly, 'prefer-readonly-parameter-types': preferReadonlyParameterTypes, 'prefer-reduce-type-parameter': preferReduceTypeParameter, diff --git a/packages/eslint-plugin/src/rules/no-throw-literal.ts b/packages/eslint-plugin/src/rules/no-throw-literal.ts index f1129c252036..f3f5937c7379 100644 --- a/packages/eslint-plugin/src/rules/no-throw-literal.ts +++ b/packages/eslint-plugin/src/rules/no-throw-literal.ts @@ -5,6 +5,7 @@ import * as ts from 'typescript'; import { createRule, getParserServices, + isErrorLike, isTypeAnyType, isTypeUnknownType, } from '../util'; @@ -55,41 +56,6 @@ export default createRule({ ], create(context, [options]) { const services = getParserServices(context); - const checker = services.program.getTypeChecker(); - - function isErrorLike(type: ts.Type): boolean { - if (type.isIntersection()) { - return type.types.some(isErrorLike); - } - if (type.isUnion()) { - return type.types.every(isErrorLike); - } - - const symbol = type.getSymbol(); - if (!symbol) { - return false; - } - - if (symbol.getName() === 'Error') { - const declarations = symbol.getDeclarations() ?? []; - for (const declaration of declarations) { - const sourceFile = declaration.getSourceFile(); - if (services.program.isSourceFileDefaultLibrary(sourceFile)) { - return true; - } - } - } - - if (symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface)) { - for (const baseType of checker.getBaseTypes(type as ts.InterfaceType)) { - if (isErrorLike(baseType)) { - return true; - } - } - } - - return false; - } function checkThrowArgument(node: TSESTree.Node): void { if ( @@ -114,7 +80,7 @@ export default createRule({ return; } - if (isErrorLike(type)) { + if (isErrorLike(services.program, type)) { return; } diff --git a/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts new file mode 100644 index 000000000000..69494823e023 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-promise-reject-errors.ts @@ -0,0 +1,153 @@ +import type { TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; +import { getDeclaredVariables } from '@typescript-eslint/utils/eslint-utils'; + +import { + createRule, + getParserServices, + isErrorLike, + isFunction, + isIdentifier, + isPromiseConstructorLike, + isPromiseLike, + isReadonlyErrorLike, +} from '../util'; + +export type MessageIds = 'rejectAnError'; + +export type Options = [ + { + allowEmptyReject?: boolean; + }, +]; + +export default createRule({ + name: 'prefer-promise-reject-errors', + meta: { + type: 'suggestion', + docs: { + description: 'Require using Error objects as Promise rejection reasons', + recommended: 'strict', + extendsBaseRule: true, + requiresTypeChecking: true, + }, + schema: [ + { + type: 'object', + properties: { + allowEmptyReject: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], + messages: { + rejectAnError: 'Expected the Promise rejection reason to be an Error.', + }, + }, + defaultOptions: [ + { + allowEmptyReject: false, + }, + ], + create(context, [options]) { + const services = getParserServices(context); + + function checkRejectCall(callExpression: TSESTree.CallExpression): void { + const argument = callExpression.arguments.at(0); + if (argument) { + const type = services.getTypeAtLocation(argument); + if ( + isErrorLike(services.program, type) || + isReadonlyErrorLike(services.program, type) + ) { + return; + } + } else if (options.allowEmptyReject) { + return; + } + + context.report({ + node: callExpression, + messageId: 'rejectAnError', + }); + } + + function skipChainExpression( + node: T, + ): T | TSESTree.ChainElement { + return node.type === AST_NODE_TYPES.ChainExpression + ? node.expression + : node; + } + + function typeAtLocationIsLikePromise(node: TSESTree.Node): boolean { + const type = services.getTypeAtLocation(node); + return ( + isPromiseConstructorLike(services.program, type) || + isPromiseLike(services.program, type) + ); + } + + return { + CallExpression(node): void { + const callee = skipChainExpression(node.callee); + + if (callee.type !== AST_NODE_TYPES.MemberExpression) { + return; + } + + const rejectMethodCalled = callee.computed + ? callee.property.type === AST_NODE_TYPES.Literal && + callee.property.value === 'reject' + : callee.property.name === 'reject'; + + if ( + !rejectMethodCalled || + !typeAtLocationIsLikePromise(callee.object) + ) { + return; + } + + checkRejectCall(node); + }, + NewExpression(node): void { + const callee = skipChainExpression(node.callee); + if ( + !isPromiseConstructorLike( + services.program, + services.getTypeAtLocation(callee), + ) + ) { + return; + } + + const executor = node.arguments.at(0); + if (!executor || !isFunction(executor)) { + return; + } + const rejectParamNode = executor.params.at(1); + if (!rejectParamNode || !isIdentifier(rejectParamNode)) { + return; + } + + // reject param is always present in variables declared by executor + const rejectVariable = getDeclaredVariables(context, executor).find( + variable => variable.identifiers.includes(rejectParamNode), + )!; + + rejectVariable.references.forEach(ref => { + if ( + ref.identifier.parent.type !== AST_NODE_TYPES.CallExpression || + ref.identifier !== ref.identifier.parent.callee + ) { + return; + } + + checkRejectCall(ref.identifier.parent); + }); + }, + }; + }, +}); 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 new file mode 100644 index 000000000000..69eed002ebf7 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/prefer-promise-reject-errors.test.ts @@ -0,0 +1,1438 @@ +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; +import { AST_NODE_TYPES } from '@typescript-eslint/utils'; + +import rule from '../../src/rules/prefer-promise-reject-errors'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2015, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('prefer-promise-reject-errors', rule, { + valid: [ + 'Promise.resolve(5);', + { + code: 'Promise.reject();', + options: [ + { + allowEmptyReject: true, + }, + ], + }, + 'Promise.reject(new Error());', + 'Promise.reject(new TypeError());', + "Promise.reject(new Error('foo'));", + ` + class CustomError extends Error {} + Promise.reject(new CustomError()); + `, + ` + declare const foo: () => { err: SyntaxError }; + Promise.reject(foo().err); + `, + ` + declare const foo: () => Promise; + Promise.reject(await foo()); + `, + 'Promise.reject((foo = new Error()));', + ` + const foo = Promise; + foo.reject(new Error()); + `, + "Promise['reject'](new Error());", + 'Promise.reject(true && new Error());', + ` + const foo = false; + Promise.reject(false || new Error()); + `, + ` + declare const foo: Readonly; + Promise.reject(foo); + `, + ` + declare const foo: Readonly | Readonly; + Promise.reject(foo); + `, + ` + declare const foo: Readonly & Readonly; + Promise.reject(foo); + `, + ` + declare const foo: Readonly & { foo: 'bar' }; + Promise.reject(foo); + `, + ` + declare const foo: Readonly & { foo: 'bar' }; + Promise.reject(foo); + `, + ` + declare const foo: Readonly>; + Promise.reject(foo); + `, + ` + declare const foo: Readonly>>; + Promise.reject(foo); + `, + ` + declare const foo: Readonly< + Readonly & { foo: 'bar' }> & { + fooBar: 'barFoo'; + } + > & { barFoo: 'fooBar' }; + Promise.reject(foo); + `, + ` + declare const foo: + | Readonly | Readonly> + | Readonly; + Promise.reject(foo); + `, + ` + type Wrapper = { foo: Readonly[] }; + declare const foo: Wrapper['foo'][5]; + Promise.reject(foo); + `, + ` + declare const foo: Error[]; + Promise.reject(foo[5]); + `, + ` + declare const foo: ReadonlyArray; + Promise.reject(foo[5]); + `, + ` + declare const foo: [Error]; + Promise.reject(foo[0]); + `, + + ` + new Promise(function (resolve, reject) { + resolve(5); + }); + `, + ` + new Promise(function (resolve, reject) { + reject(new Error()); + }); + `, + ` + new Promise((resolve, reject) => { + reject(new Error()); + }); + `, + 'new Promise((resolve, reject) => reject(new Error()));', + { + code: ` + new Promise(function (resolve, reject) { + reject(); + }); + `, + options: [ + { + allowEmptyReject: true, + }, + ], + }, + 'new Promise((yes, no) => no(new Error()));', + 'new Promise();', + 'new Promise(5);', + 'new Promise((resolve, { apply }) => {});', + 'new Promise((resolve, reject) => {});', + 'new Promise((resolve, reject) => reject);', + ` + class CustomError extends Error {} + new Promise(function (resolve, reject) { + reject(new CustomError()); + }); + `, + ` + declare const foo: () => { err: SyntaxError }; + new Promise(function (resolve, reject) { + reject(foo().err); + }); + `, + 'new Promise((resolve, reject) => reject((foo = new Error())));', + ` + new Foo((resolve, reject) => reject(5)); + `, + ` + class Foo { + constructor( + executor: (resolve: () => void, reject: (reason?: any) => void) => void, + ): Promise {} + } + new Foo((resolve, reject) => reject(5)); + `, + ` + new Promise((resolve, reject) => { + return function (reject) { + reject(5); + }; + }); + `, + 'new Promise((resolve, reject) => resolve(5, reject));', + ` + class C { + #error: Error; + foo() { + Promise.reject(this.#error); + } + } + `, + ` + const foo = Promise; + new foo((resolve, reject) => reject(new Error())); + `, + ` + declare const foo: Readonly; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly | Readonly; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly & Readonly; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly & { foo: 'bar' }; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly & { foo: 'bar' }; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly>; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly>>; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Readonly< + Readonly & { foo: 'bar' }> & { + fooBar: 'barFoo'; + } + > & { barFoo: 'fooBar' }; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: + | Readonly | Readonly> + | Readonly; + new Promise((resolve, reject) => reject(foo)); + `, + ` + type Wrapper = { foo: Readonly[] }; + declare const foo: Wrapper['foo'][5]; + new Promise((resolve, reject) => reject(foo)); + `, + ` + declare const foo: Error[]; + new Promise((resolve, reject) => reject(foo[5])); + `, + ` + declare const foo: ReadonlyArray; + new Promise((resolve, reject) => reject(foo[5])); + `, + ` + declare const foo: [Error]; + new Promise((resolve, reject) => reject(foo[0])); + `, + ` + class Foo extends Promise {} + Foo.reject(new Error()); + `, + ` + class Foo extends Promise {} + new Foo((resolve, reject) => reject(new Error())); + `, + ` + declare const someRandomCall: { + reject(arg: any): void; + }; + someRandomCall.reject(5); + `, + ` + declare const foo: PromiseConstructor; + foo.reject(new Error()); + `, + ], + invalid: [ + { + code: 'Promise.reject(5);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 18, + }, + ], + }, + { + code: "Promise.reject('foo');", + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 22, + }, + ], + }, + { + code: 'Promise.reject(`foo`);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 22, + }, + ], + }, + { + code: "Promise.reject('foo', somethingElse);", + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 37, + }, + ], + }, + { + code: 'Promise.reject(false);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 22, + }, + ], + }, + { + code: 'Promise.reject(void `foo`);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 27, + }, + ], + }, + { + code: 'Promise.reject();', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 17, + }, + ], + }, + { + code: 'Promise.reject(undefined);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 26, + }, + ], + }, + { + code: 'Promise.reject(undefined);', + options: [{ allowEmptyReject: true }], + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 26, + }, + ], + }, + { + code: 'Promise.reject(null);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 21, + }, + ], + }, + { + code: 'Promise.reject({ foo: 1 });', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 27, + }, + ], + }, + { + code: 'Promise.reject([1, 2, 3]);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 26, + }, + ], + }, + { + code: ` +declare const foo: Error | undefined; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: () => Promise; +Promise.reject(await foo()); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 28, + }, + ], + }, + { + code: ` +declare const foo: boolean; +Promise.reject(foo && new Error()); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 35, + }, + ], + }, + { + code: ` +const foo = Promise; +foo.reject(); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 13, + }, + ], + }, + + { + code: 'Promise.reject?.(5);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: 'Promise?.reject(5);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 19, + }, + ], + }, + { + code: 'Promise?.reject?.(5);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 21, + }, + ], + }, + { + code: '(Promise?.reject)(5);', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 21, + }, + ], + }, + { + code: noFormat`(Promise?.reject)?.(5);`, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 23, + }, + ], + }, + { + code: "Promise['reject'](5);", + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 21, + }, + ], + }, + + // Assignments with mathematical operators will either evaluate to a primitive value or throw a TypeError + { + code: 'Promise.reject((foo += new Error()));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 37, + }, + ], + }, + { + code: 'Promise.reject((foo -= new Error()));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 37, + }, + ], + }, + { + code: 'Promise.reject((foo **= new Error()));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 38, + }, + ], + }, + { + code: 'Promise.reject((foo <<= new Error()));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 38, + }, + ], + }, + { + code: 'Promise.reject((foo |= new Error()));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 37, + }, + ], + }, + { + code: 'Promise.reject((foo &= new Error()));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 1, + endColumn: 37, + }, + ], + }, + { + code: ` +declare const foo: never; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: unknown; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +type FakeReadonly = { 'fake readonly': T }; +declare const foo: FakeReadonly; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly<'error'>; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly | 'error'; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly | Readonly | Readonly<'error'>; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly>; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly | 'error'>>; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly & TypeError>> | 'error'; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Readonly> | Readonly | 'error'; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +type Wrapper = { foo: Readonly[] }; +declare const foo: Wrapper['foo'][5]; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: Error[]; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: ReadonlyArray; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + { + code: ` +declare const foo: [Error]; +Promise.reject(foo); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 20, + }, + ], + }, + + { + code: ` +new Promise(function (resolve, reject) { + reject(); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 3, + endColumn: 11, + }, + ], + }, + { + code: ` +new Promise(function (resolve, reject) { + reject(5); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 3, + endColumn: 12, + }, + ], + }, + { + code: ` +new Promise((resolve, reject) => { + reject(); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 3, + endColumn: 11, + }, + ], + }, + { + code: 'new Promise((resolve, reject) => reject(5));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 34, + endColumn: 43, + }, + ], + }, + { + code: ` +new Promise((resolve, reject) => { + fs.readFile('foo.txt', (err, file) => { + if (err) reject('File not found'); + else resolve(file); + }); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 14, + endColumn: 38, + }, + ], + }, + { + code: 'new Promise((yes, no) => no(5));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 26, + endColumn: 31, + }, + ], + }, + { + code: 'new Promise(({ foo, bar, baz }, reject) => reject(5));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 44, + endColumn: 53, + }, + ], + }, + { + code: ` +new Promise(function (reject, reject) { + reject(5); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 3, + endColumn: 12, + }, + ], + }, + { + code: ` +new Promise(function (foo, arguments) { + arguments(5); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 3, + endColumn: 15, + }, + ], + }, + { + code: 'new Promise((foo, arguments) => arguments(5));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 33, + endColumn: 45, + }, + ], + }, + { + code: ` +new Promise(function ({}, reject) { + reject(5); +}); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 3, + endColumn: 12, + }, + ], + }, + { + code: 'new Promise(({}, reject) => reject(5));', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 29, + endColumn: 38, + }, + ], + }, + { + code: 'new Promise((resolve, reject, somethingElse = reject(5)) => {});', + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 1, + endLine: 1, + column: 47, + endColumn: 56, + }, + ], + }, + { + code: ` +declare const foo: { + bar: PromiseConstructor; +}; +new foo.bar((resolve, reject) => reject(5)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 5, + endLine: 5, + column: 34, + endColumn: 43, + }, + ], + }, + { + code: ` +declare const foo: { + bar: PromiseConstructor; +}; +new (foo?.bar)((resolve, reject) => reject(5)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 5, + endLine: 5, + column: 37, + endColumn: 46, + }, + ], + }, + { + code: ` +const foo = Promise; +new foo((resolve, reject) => reject(5)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 30, + endColumn: 39, + }, + ], + }, + { + code: ` +declare const foo: never; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: unknown; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +type FakeReadonly = { 'fake readonly': T }; +declare const foo: FakeReadonly; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly<'error'>; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly | 'error'; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly | Readonly | Readonly<'error'>; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly>; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly | 'error'>>; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly & TypeError>> | 'error'; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Readonly> | Readonly | 'error'; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +type Wrapper = { foo: Readonly[] }; +declare const foo: Wrapper['foo'][5]; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: Error[]; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: ReadonlyArray; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +declare const foo: [Error]; +new Promise((resolve, reject) => reject(foo)); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 34, + endColumn: 45, + }, + ], + }, + { + code: ` +class Foo extends Promise {} +Foo.reject(5); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 14, + }, + ], + }, + { + code: ` +declare const foo: PromiseConstructor & string; +foo.reject(5); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 3, + endLine: 3, + column: 1, + endColumn: 14, + }, + ], + }, + { + code: ` +class Foo extends Promise {} +class Bar extends Foo {} +Bar.reject(5); + `, + errors: [ + { + messageId: 'rejectAnError', + type: AST_NODE_TYPES.CallExpression, + line: 4, + endLine: 4, + column: 1, + endColumn: 14, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/prefer-promise-reject-errors.shot b/packages/eslint-plugin/tests/schema-snapshots/prefer-promise-reject-errors.shot new file mode 100644 index 000000000000..fc04d11fd3f1 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/prefer-promise-reject-errors.shot @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes prefer-promise-reject-errors 1`] = ` +" +# SCHEMA: + +[ + { + "additionalProperties": false, + "properties": { + "allowEmptyReject": { + "type": "boolean" + } + }, + "type": "object" + } +] + + +# TYPES: + +type Options = [ + { + allowEmptyReject?: boolean; + }, +]; +" +`; diff --git a/packages/type-utils/src/builtinSymbolLikes.ts b/packages/type-utils/src/builtinSymbolLikes.ts new file mode 100644 index 000000000000..3443a0d0382e --- /dev/null +++ b/packages/type-utils/src/builtinSymbolLikes.ts @@ -0,0 +1,161 @@ +import * as ts from 'typescript'; + +import { isSymbolFromDefaultLibrary } from './isSymbolFromDefaultLibrary'; + +/** + * class Foo extends Promise {} + * Foo.reject + * ^ PromiseLike + */ +export function isPromiseLike(program: ts.Program, type: ts.Type): boolean { + return isBuiltinSymbolLike(program, type, 'Promise'); +} + +/** + * const foo = Promise + * foo.reject + * ^ PromiseConstructorLike + */ +export function isPromiseConstructorLike( + program: ts.Program, + type: ts.Type, +): boolean { + return isBuiltinSymbolLike(program, type, 'PromiseConstructor'); +} + +/** + * class Foo extends Error {} + * new Foo() + * ^ ErrorLike + */ +export function isErrorLike(program: ts.Program, type: ts.Type): boolean { + return isBuiltinSymbolLike(program, type, 'Error'); +} + +/** + * type T = Readonly + * ^ ReadonlyErrorLike + */ +export function isReadonlyErrorLike( + program: ts.Program, + type: ts.Type, +): boolean { + return isReadonlyTypeLike(program, type, subtype => { + const [typeArgument] = subtype.aliasTypeArguments; + return ( + isErrorLike(program, typeArgument) || + isReadonlyErrorLike(program, typeArgument) + ); + }); +} + +/** + * type T = Readonly<{ foo: 'bar' }> + * ^ ReadonlyTypeLike + */ +export function isReadonlyTypeLike( + program: ts.Program, + type: ts.Type, + predicate?: ( + subType: ts.Type & { + aliasSymbol: ts.Symbol; + aliasTypeArguments: readonly ts.Type[]; + }, + ) => boolean, +): boolean { + return isBuiltinTypeAliasLike(program, type, subtype => { + return ( + subtype.aliasSymbol.getName() === 'Readonly' && !!predicate?.(subtype) + ); + }); +} +export function isBuiltinTypeAliasLike( + program: ts.Program, + type: ts.Type, + predicate: ( + subType: ts.Type & { + aliasSymbol: ts.Symbol; + aliasTypeArguments: readonly ts.Type[]; + }, + ) => boolean, +): boolean { + return isBuiltinSymbolLikeRecurser(program, type, subtype => { + const { aliasSymbol, aliasTypeArguments } = subtype; + + if (!aliasSymbol || !aliasTypeArguments) { + return false; + } + + if ( + isSymbolFromDefaultLibrary(program, aliasSymbol) && + predicate( + subtype as ts.Type & { + aliasSymbol: ts.Symbol; + aliasTypeArguments: readonly ts.Type[]; + }, + ) + ) { + return true; + } + + return null; + }); +} + +export function isBuiltinSymbolLike( + program: ts.Program, + type: ts.Type, + symbolName: string, +): boolean { + return isBuiltinSymbolLikeRecurser(program, type, subType => { + const symbol = subType.getSymbol(); + if (!symbol) { + return false; + } + + if ( + symbol.getName() === symbolName && + isSymbolFromDefaultLibrary(program, symbol) + ) { + return true; + } + + return null; + }); +} + +export function isBuiltinSymbolLikeRecurser( + program: ts.Program, + type: ts.Type, + predicate: (subType: ts.Type) => boolean | null, +): boolean { + if (type.isIntersection()) { + return type.types.some(t => + isBuiltinSymbolLikeRecurser(program, t, predicate), + ); + } + if (type.isUnion()) { + return type.types.every(t => + isBuiltinSymbolLikeRecurser(program, t, predicate), + ); + } + + const predicateResult = predicate(type); + if (typeof predicateResult === 'boolean') { + return predicateResult; + } + + const symbol = type.getSymbol(); + if ( + symbol && + symbol.flags & (ts.SymbolFlags.Class | ts.SymbolFlags.Interface) + ) { + const checker = program.getTypeChecker(); + for (const baseType of checker.getBaseTypes(type as ts.InterfaceType)) { + if (isBuiltinSymbolLikeRecurser(program, baseType, predicate)) { + return true; + } + } + } + return false; +} diff --git a/packages/type-utils/src/index.ts b/packages/type-utils/src/index.ts index 9fc499aa8f31..14d5b652099f 100644 --- a/packages/type-utils/src/index.ts +++ b/packages/type-utils/src/index.ts @@ -1,3 +1,4 @@ +export * from './builtinSymbolLikes'; export * from './containsAllTypesByName'; export * from './getConstrainedTypeAtLocation'; export * from './getContextualType'; @@ -6,6 +7,7 @@ export * from './getSourceFileOfNode'; export * from './getTokenAtPosition'; export * from './getTypeArguments'; export * from './getTypeName'; +export * from './isSymbolFromDefaultLibrary'; export * from './isTypeReadonly'; export * from './isUnsafeAssignment'; export * from './predicates'; diff --git a/packages/type-utils/src/isSymbolFromDefaultLibrary.ts b/packages/type-utils/src/isSymbolFromDefaultLibrary.ts new file mode 100644 index 000000000000..786ef849a2c4 --- /dev/null +++ b/packages/type-utils/src/isSymbolFromDefaultLibrary.ts @@ -0,0 +1,20 @@ +import type * as ts from 'typescript'; + +export function isSymbolFromDefaultLibrary( + program: ts.Program, + symbol: ts.Symbol | undefined, +): boolean { + if (!symbol) { + return false; + } + + const declarations = symbol.getDeclarations() ?? []; + for (const declaration of declarations) { + const sourceFile = declaration.getSourceFile(); + if (program.isSourceFileDefaultLibrary(sourceFile)) { + return true; + } + } + + return false; +} From 939902904d84eadf560912de7777ee1501fd2340 Mon Sep 17 00:00:00 2001 From: Gabriel Costa Date: Tue, 9 Jan 2024 16:51:50 -0300 Subject: [PATCH 02/13] docs: base Testing Rules documentation (#8033) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: base Testing Rules documentation * fix(eslint-plugin): add no-unsafe-unary-minus, prefer-destructuring to disable-type-checked (#8038) fix: add no-unsafe-unary-minus, prefer-destructuring to disable-type-checked * docs: testing rules review changes + build callout * docs(eslint-plugin): [require-array-sort-compare] sync rule description (#8061) * chore: resolve internal lint issues with new no-useless-template-literals rule (#8060) * docs(eslint-plugin): [require-array-sort-compare] generalize sort method names (#8062) docs: streamline * chore: update sponsors (#8069) Co-authored-by: typescript-eslint[bot] * Update docs/contributing/local-development/Local_Linking.mdx Co-authored-by: Josh Goldberg ✨ * docs: changed section name + callout type from caution to note --------- Co-authored-by: Josh Goldberg ✨ Co-authored-by: auvred <61150013+auvred@users.noreply.github.com> Co-authored-by: James <5511220+Zamiell@users.noreply.github.com> Co-authored-by: typescript-eslint[bot] <53356952+typescript-eslint[bot]@users.noreply.github.com> Co-authored-by: typescript-eslint[bot] Co-authored-by: Gabriel Costa Moura --- .../local-development/Local_Linking.mdx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/docs/contributing/local-development/Local_Linking.mdx b/docs/contributing/local-development/Local_Linking.mdx index 00908758485c..7c7b6cdaf086 100644 --- a/docs/contributing/local-development/Local_Linking.mdx +++ b/docs/contributing/local-development/Local_Linking.mdx @@ -8,6 +8,7 @@ The general strategy to do so is: 1. [Global linking](#global-linking): Use your package manager's global link command to make `@typescript-eslint/*` packages available as global symlinks. 2. [Repository linking](#repository-linking): Use your package manager's link command to reference those global symlinks in the local downstream repository. +3. [Trying rules](#trying-rules): Test your local rules and plugins by enabling them in the local downstream repository. ## Global Linking @@ -45,6 +46,27 @@ Now, you should be able to run ESLint in the local downstream repository as you To check that the local package is being used, consider adding a `console.log("Hello, world!");` to a file such as `./packages/eslint-plugin/dist/index.js` and making sure that log appears when linting the local downstream repository. ::: +## Trying Rules + +Now that you've linked the `@typescript-eslint/*` packages with your local downstream repository, the next step would be to include the rule on the local repository ESLint configuration file, e.g: + +```json title=".eslintrc.json" +{ + "rules": { + "@typescript-eslint/your-awesome-rule": "error" + } + // ... +} +``` + +After that, you need to run your repository's `lint` script and your changes should be reflected on the project. + +:::note +Changes to `@typescript-eslint/` packages will not be reflected inside your linked repository until they're built locally. +To re-build all packages, run `yarn build` from the root. +To start a watch mode builder on just the ESLint plugin, run `yarn build --watch` from `./packages/eslint-plugin`. +::: + ## Troubleshooting ### Packages Not Found (`Cannot find module`) From e0f591e9d299a42bcf2087200b0770d810af2e64 Mon Sep 17 00:00:00 2001 From: StyleShit <32631382+StyleShit@users.noreply.github.com> Date: Tue, 9 Jan 2024 22:01:34 +0200 Subject: [PATCH 03/13] feat(eslint-plugin): [no-array-delete] add new rule (#8067) * feat(eslint-plugin): [no-array-delete] add new rule * small refactor * add more cases * fix docs * fix message * use suggestion instead of fix * added more test cases * remove redundant condition * keep comments --- .../docs/rules/no-array-delete.md | 40 ++ packages/eslint-plugin/src/configs/all.ts | 1 + .../src/configs/disable-type-checked.ts | 1 + .../src/configs/strict-type-checked.ts | 1 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-array-delete.ts | 112 ++++ .../tests/rules/no-array-delete.test.ts | 599 ++++++++++++++++++ .../schema-snapshots/no-array-delete.shot | 14 + 8 files changed, 770 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-array-delete.md create mode 100644 packages/eslint-plugin/src/rules/no-array-delete.ts create mode 100644 packages/eslint-plugin/tests/rules/no-array-delete.test.ts create mode 100644 packages/eslint-plugin/tests/schema-snapshots/no-array-delete.shot diff --git a/packages/eslint-plugin/docs/rules/no-array-delete.md b/packages/eslint-plugin/docs/rules/no-array-delete.md new file mode 100644 index 000000000000..3abbbc6ececc --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-array-delete.md @@ -0,0 +1,40 @@ +--- +description: 'Disallow using the `delete` operator on array values.' +--- + +> 🛑 This file is source code, not the primary documentation location! 🛑 +> +> See **https://typescript-eslint.io/rules/no-array-delete** for documentation. + +When using the `delete` operator with an array value, the array's `length` property is not affected, +but the element at the specified index is removed and leaves an empty slot in the array. +This is likely to lead to unexpected behavior. As mentioned in the +[MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/delete#deleting_array_elements), +the recommended way to remove an element from an array is by using the +[`Array#splice`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) method. + +## Examples + + + +### ❌ Incorrect + +```ts +declare const arr: number[]; + +delete arr[0]; +``` + +### ✅ Correct + +```ts +declare const arr: number[]; + +arr.splice(0, 1); +``` + + + +## When Not To Use It + +When you want to allow the delete operator with array expressions. diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts index 31bf124f451c..3a5e6343b197 100644 --- a/packages/eslint-plugin/src/configs/all.ts +++ b/packages/eslint-plugin/src/configs/all.ts @@ -39,6 +39,7 @@ export = { '@typescript-eslint/naming-convention': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', + '@typescript-eslint/no-array-delete': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-confusing-non-null-assertion': 'error', '@typescript-eslint/no-confusing-void-expression': 'error', diff --git a/packages/eslint-plugin/src/configs/disable-type-checked.ts b/packages/eslint-plugin/src/configs/disable-type-checked.ts index 073c8b10c8a9..4cd82bf2414e 100644 --- a/packages/eslint-plugin/src/configs/disable-type-checked.ts +++ b/packages/eslint-plugin/src/configs/disable-type-checked.ts @@ -12,6 +12,7 @@ export = { '@typescript-eslint/consistent-type-exports': 'off', '@typescript-eslint/dot-notation': 'off', '@typescript-eslint/naming-convention': 'off', + '@typescript-eslint/no-array-delete': 'off', '@typescript-eslint/no-base-to-string': 'off', '@typescript-eslint/no-confusing-void-expression': 'off', '@typescript-eslint/no-duplicate-type-constituents': 'off', diff --git a/packages/eslint-plugin/src/configs/strict-type-checked.ts b/packages/eslint-plugin/src/configs/strict-type-checked.ts index 8bf73cbe7ee6..a0f82563b1f3 100644 --- a/packages/eslint-plugin/src/configs/strict-type-checked.ts +++ b/packages/eslint-plugin/src/configs/strict-type-checked.ts @@ -13,6 +13,7 @@ export = { '@typescript-eslint/ban-types': 'error', 'no-array-constructor': 'off', '@typescript-eslint/no-array-constructor': 'error', + '@typescript-eslint/no-array-delete': 'error', '@typescript-eslint/no-base-to-string': 'error', '@typescript-eslint/no-confusing-void-expression': 'error', '@typescript-eslint/no-duplicate-enum-values': 'error', diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index d162be286e8e..94cce184242d 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -34,6 +34,7 @@ import memberOrdering from './member-ordering'; import methodSignatureStyle from './method-signature-style'; import namingConvention from './naming-convention'; import noArrayConstructor from './no-array-constructor'; +import noArrayDelete from './no-array-delete'; import noBaseToString from './no-base-to-string'; import confusingNonNullAssertionLikeNotEqual from './no-confusing-non-null-assertion'; import noConfusingVoidExpression from './no-confusing-void-expression'; @@ -174,6 +175,7 @@ export default { 'method-signature-style': methodSignatureStyle, 'naming-convention': namingConvention, 'no-array-constructor': noArrayConstructor, + 'no-array-delete': noArrayDelete, 'no-base-to-string': noBaseToString, 'no-confusing-non-null-assertion': confusingNonNullAssertionLikeNotEqual, 'no-confusing-void-expression': noConfusingVoidExpression, diff --git a/packages/eslint-plugin/src/rules/no-array-delete.ts b/packages/eslint-plugin/src/rules/no-array-delete.ts new file mode 100644 index 000000000000..141332e06382 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-array-delete.ts @@ -0,0 +1,112 @@ +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; +import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; +import { getSourceCode } from '@typescript-eslint/utils/eslint-utils'; +import type * as ts from 'typescript'; + +import { + createRule, + getConstrainedTypeAtLocation, + getParserServices, +} from '../util'; + +type MessageId = 'noArrayDelete' | 'useSplice'; + +export default createRule<[], MessageId>({ + name: 'no-array-delete', + meta: { + hasSuggestions: true, + type: 'problem', + docs: { + description: 'Disallow using the `delete` operator on array values', + recommended: 'strict', + requiresTypeChecking: true, + }, + messages: { + noArrayDelete: + 'Using the `delete` operator with an array expression is unsafe.', + useSplice: 'Use `array.splice()` instead.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const services = getParserServices(context); + const checker = services.program.getTypeChecker(); + + function isUnderlyingTypeArray(type: ts.Type): boolean { + const predicate = (t: ts.Type): boolean => + checker.isArrayType(t) || checker.isTupleType(t); + + if (type.isUnion()) { + return type.types.every(predicate); + } + + if (type.isIntersection()) { + return type.types.some(predicate); + } + + return predicate(type); + } + + return { + 'UnaryExpression[operator="delete"]'( + node: TSESTree.UnaryExpression, + ): void { + const { argument } = node; + + if (argument.type !== AST_NODE_TYPES.MemberExpression) { + return; + } + + const type = getConstrainedTypeAtLocation(services, argument.object); + + if (!isUnderlyingTypeArray(type)) { + return; + } + + context.report({ + node, + messageId: 'noArrayDelete', + suggest: [ + { + messageId: 'useSplice', + fix(fixer): TSESLint.RuleFix | null { + const { object, property } = argument; + + const shouldHaveParentheses = + property.type === AST_NODE_TYPES.SequenceExpression; + + const nodeMap = services.esTreeNodeToTSNodeMap; + const target = nodeMap.get(object).getText(); + const rawKey = nodeMap.get(property).getText(); + const key = shouldHaveParentheses ? `(${rawKey})` : rawKey; + + let suggestion = `${target}.splice(${key}, 1)`; + + const sourceCode = getSourceCode(context); + const comments = sourceCode.getCommentsInside(node); + + if (comments.length > 0) { + const indentationCount = node.loc.start.column; + const indentation = ' '.repeat(indentationCount); + + const commentsText = comments + .map(comment => { + return comment.type === AST_TOKEN_TYPES.Line + ? `//${comment.value}` + : `/*${comment.value}*/`; + }) + .join(`\n${indentation}`); + + suggestion = `${commentsText}\n${indentation}${suggestion}`; + } + + return fixer.replaceText(node, suggestion); + }, + }, + ], + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-array-delete.test.ts b/packages/eslint-plugin/tests/rules/no-array-delete.test.ts new file mode 100644 index 000000000000..ac803cb5b2f8 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-array-delete.test.ts @@ -0,0 +1,599 @@ +import { noFormat, RuleTester } from '@typescript-eslint/rule-tester'; + +import rule from '../../src/rules/no-array-delete'; +import { getFixturesRootDir } from '../RuleTester'; + +const rootPath = getFixturesRootDir(); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +ruleTester.run('no-array-delete', rule, { + valid: [ + ` + declare const obj: { a: 1; b: 2 }; + delete obj.a; + `, + + ` + declare const obj: { a: 1; b: 2 }; + delete obj['a']; + `, + + ` + declare const arr: { a: 1; b: 2 }[][][][]; + delete arr[0][0][0][0].a; + `, + + ` + declare const maybeArray: any; + delete maybeArray[0]; + `, + + ` + declare const maybeArray: unknown; + delete maybeArray[0]; + `, + + ` + declare function getObject(): T; + delete getObject().a; + `, + + ` + declare function getObject(): { a: T; b: 2 }; + delete getObject().a; + `, + + ` + declare const test: never; + delete test[0]; + `, + ], + + invalid: [ + { + code: ` + declare const arr: number[]; + delete arr[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: number[]; + arr.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: number[]; + declare const key: number; + delete arr[key]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 4, + column: 9, + endColumn: 24, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: number[]; + declare const key: number; + arr.splice(key, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: number[]; + + enum Keys { + A, + B, + } + + delete arr[Keys.A]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 9, + column: 9, + endColumn: 27, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: number[]; + + enum Keys { + A, + B, + } + + arr.splice(Keys.A, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: number[]; + declare function doWork(): void; + delete arr[(doWork(), 1)]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 4, + column: 9, + endColumn: 34, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: number[]; + declare function doWork(): void; + arr.splice((doWork(), 1), 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: Array; + delete arr[0]; + `, + + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: Array; + arr.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: 'delete [1, 2, 3][0];', + errors: [ + { + messageId: 'noArrayDelete', + line: 1, + column: 1, + endColumn: 20, + suggestions: [ + { + messageId: 'useSplice', + output: '[1, 2, 3].splice(0, 1);', + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: unknown[]; + delete arr[Math.random() ? 0 : 1]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 42, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: unknown[]; + arr.splice(Math.random() ? 0 : 1, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: number[] | string[] | boolean[]; + delete arr[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: number[] | string[] | boolean[]; + arr.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: number[] & unknown; + delete arr[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: number[] & unknown; + arr.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const arr: (number | string)[]; + delete arr[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const arr: (number | string)[]; + arr.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const obj: { a: { b: { c: number[] } } }; + delete obj.a.b.c[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 28, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const obj: { a: { b: { c: number[] } } }; + obj.a.b.c.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare function getArray(): T; + delete getArray()[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 29, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare function getArray(): T; + getArray().splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare function getArray(): T[]; + delete getArray()[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 29, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare function getArray(): T[]; + getArray().splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + function deleteFromArray(a: number[]) { + delete a[0]; + } + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 11, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + function deleteFromArray(a: number[]) { + a.splice(0, 1); + } + `, + }, + ], + }, + ], + }, + + { + code: ` + function deleteFromArray(a: T[]) { + delete a[0]; + } + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 11, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + function deleteFromArray(a: T[]) { + a.splice(0, 1); + } + `, + }, + ], + }, + ], + }, + + { + code: ` + function deleteFromArray(a: T) { + delete a[0]; + } + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 11, + endColumn: 22, + suggestions: [ + { + messageId: 'useSplice', + output: ` + function deleteFromArray(a: T) { + a.splice(0, 1); + } + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const tuple: [number, string]; + delete tuple[0]; + `, + errors: [ + { + messageId: 'noArrayDelete', + line: 3, + column: 9, + endColumn: 24, + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const tuple: [number, string]; + tuple.splice(0, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const a: number[]; + declare const b: number; + + delete [...a, ...a][b]; + `, + errors: [ + { + messageId: 'noArrayDelete', + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const a: number[]; + declare const b: number; + + [...a, ...a].splice(b, 1); + `, + }, + ], + }, + ], + }, + + { + code: noFormat` + declare const a: number[]; + declare const b: number; + + // before expression + delete /** multi + line */ a[(( + // single-line + b /* inline */ /* another-inline */ ) + ) /* another-one */ ] /* before semicolon */; /* after semicolon */ + // after expression + `, + errors: [ + { + messageId: 'noArrayDelete', + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const a: number[]; + declare const b: number; + + // before expression + /** multi + line */ + // single-line + /* inline */ + /* another-inline */ + /* another-one */ + a.splice(b, 1) /* before semicolon */; /* after semicolon */ + // after expression + `, + }, + ], + }, + ], + }, + + { + code: noFormat` + declare const a: number[]; + declare const b: number; + + delete ((a[((b))])); + `, + errors: [ + { + messageId: 'noArrayDelete', + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const a: number[]; + declare const b: number; + + a.splice(b, 1); + `, + }, + ], + }, + ], + }, + + { + code: ` + declare const a: number[]; + declare const b: number; + + delete a[(b + 1) * (b + 2)]; + `, + errors: [ + { + messageId: 'noArrayDelete', + suggestions: [ + { + messageId: 'useSplice', + output: ` + declare const a: number[]; + declare const b: number; + + a.splice((b + 1) * (b + 2), 1); + `, + }, + ], + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-array-delete.shot b/packages/eslint-plugin/tests/schema-snapshots/no-array-delete.shot new file mode 100644 index 000000000000..c8dc106464a0 --- /dev/null +++ b/packages/eslint-plugin/tests/schema-snapshots/no-array-delete.shot @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Rule schemas should be convertible to TS types for documentation purposes no-array-delete 1`] = ` +" +# SCHEMA: + +[] + + +# TYPES: + +/** No options declared */ +type Options = [];" +`; From 8ca5e5e8430ba1beb5f12f317d403d7087bf9210 Mon Sep 17 00:00:00 2001 From: kirkwaiblinger <53019676+kirkwaiblinger@users.noreply.github.com> Date: Tue, 9 Jan 2024 13:09:34 -0700 Subject: [PATCH 04/13] docs: force space after await in no-floating-promises snippet (#8228) force space after await --- packages/eslint-plugin/docs/rules/no-floating-promises.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.md b/packages/eslint-plugin/docs/rules/no-floating-promises.md index e137a85aaf97..da46d7f98d18 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.md +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.md @@ -98,8 +98,9 @@ This allows you to skip checking of async IIFEs (Immediately Invoked function Ex Examples of **correct** code for this rule with `{ ignoreIIFE: true }`: + ```ts option='{ "ignoreIIFE": true }' showPlaygroundButton -await(async function () { +await (async function () { await res(1); })(); From c3767edf65716be08df25723d7dbb770de0e7037 Mon Sep 17 00:00:00 2001 From: StyleShit <32631382+StyleShit@users.noreply.github.com> Date: Tue, 9 Jan 2024 23:02:47 +0200 Subject: [PATCH 05/13] feat(eslint-plugin): [no-useless-template-literals] add fix suggestions (#8065) * feat(eslint-plugin): [no-useless-template-literals] add fix suggestions * change message * add quasis to cspell * add some test cases * use fix instead of suggestion --- .cspell.json | 1 + .../src/rules/no-useless-template-literals.ts | 55 ++++- .../no-useless-template-literals.test.ts | 202 ++++++++++++++++-- 3 files changed, 244 insertions(+), 14 deletions(-) diff --git a/.cspell.json b/.cspell.json index 6133a8917ab4..c6623037c42c 100644 --- a/.cspell.json +++ b/.cspell.json @@ -107,6 +107,7 @@ "preact", "Premade", "prettier's", + "quasis", "Quickstart", "recurse", "redeclaration", diff --git a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts index 48c714edb90a..d5a8a602b6b4 100644 --- a/packages/eslint-plugin/src/rules/no-useless-template-literals.ts +++ b/packages/eslint-plugin/src/rules/no-useless-template-literals.ts @@ -1,4 +1,4 @@ -import type { TSESTree } from '@typescript-eslint/utils'; +import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; @@ -15,7 +15,8 @@ type MessageId = 'noUselessTemplateLiteral'; export default createRule<[], MessageId>({ name: 'no-useless-template-literals', meta: { - type: 'problem', + fixable: 'code', + type: 'suggestion', docs: { description: 'Disallow unnecessary template literals', recommended: 'strict', @@ -68,6 +69,22 @@ export default createRule<[], MessageId>({ context.report({ node: node.expressions[0], messageId: 'noUselessTemplateLiteral', + fix(fixer): TSESLint.RuleFix[] { + const [prevQuasi, nextQuasi] = node.quasis; + + // Remove the quasis and backticks. + return [ + fixer.removeRange([ + prevQuasi.range[1] - 3, + node.expressions[0].range[0], + ]), + + fixer.removeRange([ + node.expressions[0].range[1], + nextQuasi.range[0] + 2, + ]), + ]; + }, }); return; @@ -83,6 +100,40 @@ export default createRule<[], MessageId>({ context.report({ node: expression, messageId: 'noUselessTemplateLiteral', + fix(fixer): TSESLint.RuleFix[] { + const index = node.expressions.indexOf(expression); + const prevQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + + // Remove the quasis' parts that are related to the current expression. + const fixes = [ + fixer.removeRange([ + prevQuasi.range[1] - 2, + expression.range[0], + ]), + + fixer.removeRange([ + expression.range[1], + nextQuasi.range[0] + 1, + ]), + ]; + + // Remove quotes for string literals (i.e. `'a'` will become `a`). + const isStringLiteral = + isUnderlyingTypeString(expression) && + expression.type === AST_NODE_TYPES.Literal; + + if (isStringLiteral) { + const escapedValue = expression.value.replace( + /([`$\\])/g, + '\\$1', + ); + + fixes.push(fixer.replaceText(expression, escapedValue)); + } + + return fixes; + }, }); }); }, diff --git a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts index 54ac89c2c797..674b4ccefe77 100644 --- a/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts +++ b/packages/eslint-plugin/tests/rules/no-useless-template-literals.test.ts @@ -137,6 +137,7 @@ ruleTester.run('no-useless-template-literals', rule, { invalid: [ { code: '`${1}`;', + output: '`1`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -146,19 +147,50 @@ ruleTester.run('no-useless-template-literals', rule, { }, ], }, + { - code: '`${1n}`;', + code: noFormat`\`\${ 1 }\`;`, + output: '`1`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: noFormat`\`\${ 'a' }\`;`, + output: `'a';`, errors: [ { messageId: 'noUselessTemplateLiteral', - line: 1, - column: 4, - endColumn: 6, }, ], }, + + { + code: noFormat`\`\${ "a" }\`;`, + output: `"a";`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: noFormat`\`\${ 'a' + 'b' }\`;`, + output: `'a' + 'b';`, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + { code: '`${true}`;', + output: '`true`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -168,8 +200,20 @@ ruleTester.run('no-useless-template-literals', rule, { }, ], }, + + { + code: noFormat`\`\${ true }\`;`, + output: '`true`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + { code: '`${null}`;', + output: '`null`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -179,8 +223,20 @@ ruleTester.run('no-useless-template-literals', rule, { }, ], }, + + { + code: noFormat`\`\${ null }\`;`, + output: '`null`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + { code: '`${undefined}`;', + output: '`undefined`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -190,8 +246,20 @@ ruleTester.run('no-useless-template-literals', rule, { }, ], }, + { - code: "`${'a'}${'b'}`;", + code: noFormat`\`\${ undefined }\`;`, + output: '`undefined`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: "`${'a'} ${'b'}`;", + output: '`a b`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -202,8 +270,21 @@ ruleTester.run('no-useless-template-literals', rule, { { messageId: 'noUselessTemplateLiteral', line: 1, - column: 10, - endColumn: 13, + column: 11, + endColumn: 14, + }, + ], + }, + + { + code: noFormat`\`\${ 'a' } \${ 'b' }\`;`, + output: '`a b`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + { + messageId: 'noUselessTemplateLiteral', }, ], }, @@ -213,6 +294,10 @@ ruleTester.run('no-useless-template-literals', rule, { declare const b: 'b'; \`a\${b}\${'c'}\`; `, + output: ` + declare const b: 'b'; + \`a\${b}c\`; + `, errors: [ { messageId: 'noUselessTemplateLiteral', @@ -225,6 +310,7 @@ ruleTester.run('no-useless-template-literals', rule, { { code: "`a${'b'}`;", + output: '`ab`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -236,13 +322,14 @@ ruleTester.run('no-useless-template-literals', rule, { }, { - code: "`${'1 + 1 = '}${2}`;", + code: "`${'1 + 1 ='} ${2}`;", + output: '`1 + 1 = 2`;', errors: [ { messageId: 'noUselessTemplateLiteral', line: 1, column: 4, - endColumn: 14, + endColumn: 13, }, { messageId: 'noUselessTemplateLiteral', @@ -254,7 +341,8 @@ ruleTester.run('no-useless-template-literals', rule, { }, { - code: "`${'a'}${true}`;", + code: "`${'a'} ${true}`;", + output: '`a true`;', errors: [ { messageId: 'noUselessTemplateLiteral', @@ -265,8 +353,8 @@ ruleTester.run('no-useless-template-literals', rule, { { messageId: 'noUselessTemplateLiteral', line: 1, - column: 10, - endColumn: 14, + column: 11, + endColumn: 15, }, ], }, @@ -276,6 +364,10 @@ ruleTester.run('no-useless-template-literals', rule, { declare const string: 'a'; \`\${string}\`; `, + output: ` + declare const string: 'a'; + string; + `, errors: [ { messageId: 'noUselessTemplateLiteral', @@ -286,8 +378,25 @@ ruleTester.run('no-useless-template-literals', rule, { ], }, + { + code: noFormat` + declare const string: 'a'; + \`\${ string }\`; + `, + output: ` + declare const string: 'a'; + string; + `, + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + { code: "`${String(Symbol.for('test'))}`;", + output: "String(Symbol.for('test'));", errors: [ { messageId: 'noUselessTemplateLiteral', @@ -303,6 +412,10 @@ ruleTester.run('no-useless-template-literals', rule, { declare const intersection: string & { _brand: 'test-brand' }; \`\${intersection}\`; `, + output: ` + declare const intersection: string & { _brand: 'test-brand' }; + intersection; + `, errors: [ { messageId: 'noUselessTemplateLiteral', @@ -319,6 +432,11 @@ ruleTester.run('no-useless-template-literals', rule, { \`\${arg}\`; } `, + output: ` + function func(arg: T) { + arg; + } + `, errors: [ { messageId: 'noUselessTemplateLiteral', @@ -328,5 +446,65 @@ ruleTester.run('no-useless-template-literals', rule, { }, ], }, + + { + code: "`${'`'}`;", + output: "'`';", + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: "`back${'`'}tick`;", + output: '`back\\`tick`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: "`dollar${'${`this is test`}'}sign`;", + output: '`dollar\\${\\`this is test\\`}sign`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: '`complex${\'`${"`${test}`"}`\'}case`;', + output: '`complex\\`\\${"\\`\\${test}\\`"}\\`case`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: "`some ${'\\\\${test}'} string`;", + output: '`some \\\\\\${test} string`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, + + { + code: "`some ${'\\\\`'} string`;", + output: '`some \\\\\\` string`;', + errors: [ + { + messageId: 'noUselessTemplateLiteral', + }, + ], + }, ], }); From 96ffd2475497572904ccf098c127e7c9bc18e7b8 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Thu, 11 Jan 2024 18:56:27 +0100 Subject: [PATCH 06/13] docs: link version in website header to GitHub release (#8236) --- packages/website/docusaurusConfig.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/docusaurusConfig.ts b/packages/website/docusaurusConfig.ts index 625e6008a08f..cc283cfc2993 100644 --- a/packages/website/docusaurusConfig.ts +++ b/packages/website/docusaurusConfig.ts @@ -91,8 +91,8 @@ const themeConfig: AlgoliaThemeConfig & ThemeCommonConfig = { }, { position: 'right', - value: ``, - type: 'html', + href: `https://github.com/typescript-eslint/typescript-eslint/releases/tag/v${version}`, + label: `v${version}`, }, { to: 'play', From e80d54e5f3164f132fc544f40aabbe3f88ef4f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Thu, 11 Jan 2024 19:40:57 -0500 Subject: [PATCH 07/13] fix(typescript-estree): add JSDocParsingMode enum merge for typescript/lib/tsserverlibrary (#8193) --- packages/typescript-estree/src/parseSettings/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/typescript-estree/src/parseSettings/index.ts b/packages/typescript-estree/src/parseSettings/index.ts index 6aca7a863aae..1df275901066 100644 --- a/packages/typescript-estree/src/parseSettings/index.ts +++ b/packages/typescript-estree/src/parseSettings/index.ts @@ -12,6 +12,11 @@ declare module 'typescript' { // Added in TypeScript 5.3 enum JSDocParsingMode {} } +// https://github.com/typescript-eslint/typescript-eslint/issues/8172 +declare module 'typescript/lib/tsserverlibrary' { + // Added in TypeScript 5.3 + enum JSDocParsingMode {} +} /** * Internal settings used by the parser to run on a file. From 5a5670800fda2cd5ffd060b0fb9db351f0f3c4d8 Mon Sep 17 00:00:00 2001 From: LJX <11309921+lvjiaxuan@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:40:18 +0800 Subject: [PATCH 08/13] fix(eslint-plugin): [no-unnecessary-type-assertion] detect unnecessary non-null-assertion on a call expression (#8143) * feat(eslint-plugin): [no-unnecessary-type-assertion] add `Identifier` check * feat(eslint-plugin): [no-unnecessary-type-assertion] update fixer for `CallExpression` * feat(eslint-plugin): [no-unnecessary-type-assertion] add test case * feat(eslint-plugin): [no-unnecessary-type-assertion] fit more cases * feat(eslint-plugin): [no-unnecessary-type-assertion] add valid test cases * fix: fix a gap and add tests * fix: typo --- .../src/rules/no-unnecessary-condition.ts | 2 +- .../rules/no-unnecessary-type-assertion.ts | 15 +- .../no-unnecessary-type-assertion.test.ts | 137 ++++++++++++++++++ 3 files changed, 147 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 12a42398b3ee..69dd6174fec2 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -436,7 +436,7 @@ export default createRule({ function checkCallExpression(node: TSESTree.CallExpression): void { // If this is something like arr.filter(x => /*condition*/), check `condition` if (isArrayPredicateFunction(node) && node.arguments.length) { - const callback = node.arguments[0]!; + const callback = node.arguments[0]; // Inline defined functions if ( callback.type === AST_NODE_TYPES.ArrowFunctionExpression || diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 96de86efe116..58ef344bf515 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -169,7 +169,10 @@ export default createRule({ const type = getConstrainedTypeAtLocation(services, node.expression); if (!isNullableType(type)) { - if (isPossiblyUsedBeforeAssigned(node.expression)) { + if ( + node.expression.type === AST_NODE_TYPES.Identifier && + isPossiblyUsedBeforeAssigned(node.expression) + ) { return; } @@ -177,10 +180,7 @@ export default createRule({ node, messageId: 'unnecessaryAssertion', fix(fixer) { - return fixer.removeRange([ - node.expression.range[1], - node.range[1], - ]); + return fixer.removeRange([node.range[1] - 1, node.range[1]]); }, }); } else { @@ -274,7 +274,10 @@ export default createRule({ : null; } return fixer.removeRange([ - node.expression.range[1] + 1, + node.expression.range[1] + + (node.expression.type === AST_NODE_TYPES.CallExpression + ? 0 + : 1), node.range[1], ]); }, diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 8cf362938bab..010cf2ace83a 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -213,6 +213,30 @@ let values: number[] = []; value = values.pop()!; `, + ` +declare function foo(): number | undefined; +const a = foo()!; + `, + ` +declare function foo(): number | undefined; +const a = foo() as number; + `, + ` +declare function foo(): number | undefined; +const a = foo(); + `, + ` +declare const arr: (object | undefined)[]; +const item = arr[0]!; + `, + ` +declare const arr: (object | undefined)[]; +const item = arr[0] as object; + `, + ` +declare const arr: (object | undefined)[]; +const item = arr[0]; + `, ], invalid: [ @@ -518,5 +542,118 @@ y = 0; }, ], }, + { + code: ` +declare function foo(): number; +const a = foo()!; + `, + output: ` +declare function foo(): number; +const a = foo(); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + column: 11, + endColumn: 17, + }, + ], + }, + { + code: ` +const b = new Date()!; + `, + output: ` +const b = new Date(); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 2, + }, + ], + }, + { + code: ` +const b = (1 + 1)!; + `, + output: ` +const b = (1 + 1); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 2, + column: 11, + endColumn: 19, + }, + ], + }, + { + code: ` +declare function foo(): number; +const a = foo() as number; + `, + output: ` +declare function foo(): number; +const a = foo(); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + column: 11, + }, + ], + }, + { + code: ` +declare function foo(): number; +const a = foo(); + `, + output: ` +declare function foo(): number; +const a = foo(); + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + code: ` +type RT = { log: () => void }; +declare function foo(): RT; +(foo() as RT).log; + `, + output: ` +type RT = { log: () => void }; +declare function foo(): RT; +(foo()).log; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + }, + { + code: ` +declare const arr: object[]; +const item = arr[0]!; + `, + output: ` +declare const arr: object[]; +const item = arr[0]; + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + }, + ], + }, ], }); From 7e75e84709e48dccfd099379724f8fa37b52330b Mon Sep 17 00:00:00 2001 From: Steven Date: Fri, 12 Jan 2024 19:26:37 +0800 Subject: [PATCH 09/13] fix(typescript-estree): disallow `using` as the variable keyword for `for..in` loops (#7649) Co-authored-by: Brad Zacher --- .../fixtures/_error_/using-initializer/fixture.ts | 1 + .../using-initializer/snapshots/1-TSESTree-Error.shot | 7 +++++++ .../using-initializer/snapshots/2-Babel-Error.shot | 3 +++ .../snapshots/3-Alignment-Error.shot | 3 +++ packages/typescript-estree/src/convert.ts | 11 +++++++++++ 5 files changed, 25 insertions(+) create mode 100644 packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/fixture.ts create mode 100644 packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/1-TSESTree-Error.shot create mode 100644 packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/2-Babel-Error.shot create mode 100644 packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/3-Alignment-Error.shot diff --git a/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/fixture.ts b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/fixture.ts new file mode 100644 index 000000000000..b65a9983c6cd --- /dev/null +++ b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/fixture.ts @@ -0,0 +1 @@ +for(using foo in {}); \ No newline at end of file diff --git a/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/1-TSESTree-Error.shot b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/1-TSESTree-Error.shot new file mode 100644 index 000000000000..f914c11ac282 --- /dev/null +++ b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/1-TSESTree-Error.shot @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST Fixtures statement ForInStatement _error_ using-initializer TSESTree - Error 1`] = ` +"TSError +> 1 | for(using foo in {}); + | ^^^^^^^^^ The left-hand side of a 'for...in' statement cannot be a 'using' declaration." +`; diff --git a/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/2-Babel-Error.shot b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/2-Babel-Error.shot new file mode 100644 index 000000000000..f1c91950c6ec --- /dev/null +++ b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/2-Babel-Error.shot @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST Fixtures statement ForInStatement _error_ using-initializer Babel - Error 1`] = `[SyntaxError: For-in loop may not start with 'using' declaration. (1:4)]`; diff --git a/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/3-Alignment-Error.shot b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/3-Alignment-Error.shot new file mode 100644 index 000000000000..3987fd760265 --- /dev/null +++ b/packages/ast-spec/src/statement/ForInStatement/fixtures/_error_/using-initializer/snapshots/3-Alignment-Error.shot @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AST Fixtures statement ForInStatement _error_ using-initializer Error Alignment 1`] = `"Both errored"`; diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 7ebd5cc9e1cb..2fb094c1b589 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -933,6 +933,7 @@ export class Converter { }); case SyntaxKind.ForInStatement: + this.#checkForStatementDeclaration(node.initializer); return this.createNode(node, { type: AST_NODE_TYPES.ForInStatement, left: this.convertPattern(node.initializer), @@ -3510,4 +3511,14 @@ export class Converter { throw createError(message, this.ast, start, end); } + #checkForStatementDeclaration(initializer: ts.ForInitializer): void { + if (ts.isVariableDeclarationList(initializer)) { + if ((initializer.flags & ts.NodeFlags.Using) !== 0) { + this.#throwError( + initializer, + "The left-hand side of a 'for...in' statement cannot be a 'using' declaration.", + ); + } + } + } } From 85ae4a8977149583b0c501e092a927b1435e68d2 Mon Sep 17 00:00:00 2001 From: Joshua Chen Date: Fri, 12 Jan 2024 06:26:52 -0500 Subject: [PATCH 10/13] fix(eslint-plugin): [no-unnecesary-type-assertion] treat unknown/any as nullable (#8089) Co-authored-by: Brad Zacher --- .../src/rules/no-unnecessary-condition.ts | 11 +++----- .../src/rules/prefer-nullish-coalescing.ts | 3 +-- .../no-unnecessary-type-assertion.test.ts | 5 ++++ packages/type-utils/src/predicates.ts | 26 +++++++++---------- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts index 69dd6174fec2..0a31535797d7 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-condition.ts @@ -530,7 +530,7 @@ export default createRule({ propertyType.value.toString(), ); if (propType) { - return isNullableType(propType, { allowUndefined: true }); + return isNullableType(propType); } } const typeName = getTypeName(checker, propertyType); @@ -568,14 +568,12 @@ export default createRule({ ); if (propType) { - return isNullableType(propType, { allowUndefined: true }); + return isNullableType(propType); } return !!checker.getIndexInfoOfType(type, ts.IndexKind.String); }); - return ( - !isOwnNullable && isNullableType(prevType, { allowUndefined: true }) - ); + return !isOwnNullable && isNullableType(prevType); } return false; } @@ -612,8 +610,7 @@ export default createRule({ const possiblyVoid = isTypeFlagSet(type, ts.TypeFlags.Void); return ( isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown) || - (isOwnNullable && - (isNullableType(type, { allowUndefined: true }) || possiblyVoid)) + (isOwnNullable && (isNullableType(type) || possiblyVoid)) ); } diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index be498cb94ad0..c688d75d2771 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -309,8 +309,7 @@ export default createRule({ ): void { const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); const type = checker.getTypeAtLocation(tsNode.left); - const isNullish = isNullableType(type, { allowUndefined: true }); - if (!isNullish) { + if (!isNullableType(type)) { return; } diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 010cf2ace83a..2c3cc3599e83 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -48,6 +48,11 @@ const foo = { 0: 'hello', 5: 'hello' } as PossibleTuple; ` let bar: number | undefined = x; let foo: number = bar!; + `, + ` +declare const a: { data?: unknown }; + +const x = a.data!; `, { code: ` diff --git a/packages/type-utils/src/predicates.ts b/packages/type-utils/src/predicates.ts index 37658e31b716..26ef59355781 100644 --- a/packages/type-utils/src/predicates.ts +++ b/packages/type-utils/src/predicates.ts @@ -2,15 +2,18 @@ import debug from 'debug'; import * as tsutils from 'ts-api-utils'; import * as ts from 'typescript'; -import { getTypeFlags, isTypeFlagSet } from './typeFlagUtils'; +import { isTypeFlagSet } from './typeFlagUtils'; const log = debug('typescript-eslint:eslint-plugin:utils:types'); export interface IsNullableTypeOptions { /** - * Whether the type is a receiving type (i.e. the type of a called function's parameter). + * @deprecated - this flag no longer does anything and will be removed in the next major */ isReceiver?: boolean; + /** + * @deprecated - this flag no longer does anything and will be removed in the next major + */ allowUndefined?: boolean; } @@ -19,18 +22,15 @@ export interface IsNullableTypeOptions { */ export function isNullableType( type: ts.Type, - { isReceiver = false, allowUndefined = true }: IsNullableTypeOptions = {}, + _deprecated?: IsNullableTypeOptions, ): boolean { - const flags = getTypeFlags(type); - - if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { - return true; - } - - if (allowUndefined) { - return (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; - } - return (flags & ts.TypeFlags.Null) !== 0; + return isTypeFlagSet( + type, + ts.TypeFlags.Any | + ts.TypeFlags.Unknown | + ts.TypeFlags.Null | + ts.TypeFlags.Undefined, + ); } /** From c7b0b6c9c8eb66ebd0616f66daf8bd9535826cee Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sat, 13 Jan 2024 14:23:41 +1030 Subject: [PATCH 11/13] fix(typescript-estree): fix incorrect backwards-compat augmentation in TS 5.3 (#8181) --- packages/typescript-estree/src/parser.ts | 2 +- packages/typescript-estree/src/ts-estree/ts-nodes.ts | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 08c29892e220..ffa0c4212295 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -244,7 +244,7 @@ function parseAndGenerateServices( options.filePath && parseAndGenerateServicesCalls[options.filePath] > 1 ? createIsolatedProgram(parseSettings) - : getProgramAndAST(parseSettings, hasFullTypeInformation)!; + : getProgramAndAST(parseSettings, hasFullTypeInformation); /** * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve diff --git a/packages/typescript-estree/src/ts-estree/ts-nodes.ts b/packages/typescript-estree/src/ts-estree/ts-nodes.ts index 5bf6af81a9f5..2a07ee0e7eac 100644 --- a/packages/typescript-estree/src/ts-estree/ts-nodes.ts +++ b/packages/typescript-estree/src/ts-estree/ts-nodes.ts @@ -2,12 +2,11 @@ import type * as ts from 'typescript'; // Workaround to support new TS version features for consumers on old TS versions // Eg: https://github.com/typescript-eslint/typescript-eslint/issues/2388, https://github.com/typescript-eslint/typescript-eslint/issues/2784 -/* eslint-disable @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error, @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/no-empty-interface */ declare module 'typescript' { - /** @ts-ignore - added in TS 4.5, deprecated and converted to a type-alias in TS 5.3 */ - export interface AssertClause extends ts.Node {} - /** @ts-ignore - added in TS 4.5, deprecated and converted to a type-alias in TS 5.3 */ - export interface AssertEntry extends ts.Node {} + // added in TS 4.5, deprecated in TS 5.3 + export interface AssertClause extends ts.ImportAttributes {} + export interface AssertEntry extends ts.ImportAttribute {} // added in TS 4.9 export interface SatisfiesExpression extends ts.Node {} // added in TS 5.1 @@ -16,7 +15,7 @@ declare module 'typescript' { export interface ImportAttribute extends ts.Node {} export interface ImportAttributes extends ts.Node {} } -/* eslint-enable @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error, @typescript-eslint/no-empty-interface */ +/* eslint-enable @typescript-eslint/no-empty-interface */ export type TSToken = ts.Token; From b7951535f06a39f9b3d62ecc55115cb7f9e600f1 Mon Sep 17 00:00:00 2001 From: James Henry Date: Sun, 14 Jan 2024 01:20:27 +0400 Subject: [PATCH 12/13] chore: make lint job use eslint-plugin outputs as inputs (#8245) --- nx.json | 7 ++++++- .../tools/integration-test-base.ts | 2 ++ packages/types/package.json | 18 +++++------------- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/nx.json b/nx.json index 552b09332128..d85a0833983e 100644 --- a/nx.json +++ b/nx.json @@ -38,11 +38,16 @@ "cache": true }, "lint": { + "dependsOn": ["eslint-plugin:build"], "inputs": [ "default", "{workspaceRoot}/.eslintrc.js", "{workspaceRoot}/yarn.lock", - "{workspaceRoot}/.eslintignore" + "{workspaceRoot}/.eslintignore", + { + "dependentTasksOutputFiles": "**/*.js", + "transitive": false + } ], "cache": true }, diff --git a/packages/integration-tests/tools/integration-test-base.ts b/packages/integration-tests/tools/integration-test-base.ts index eb35212ee775..833ec45182a0 100644 --- a/packages/integration-tests/tools/integration-test-base.ts +++ b/packages/integration-tests/tools/integration-test-base.ts @@ -94,6 +94,8 @@ export function integrationTest(testFilename: string, filesGlob: string): void { if (stderr.length > 0) { console.error(stderr); } + // childProcess.ExecFileException is an extension of Error + // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(err); } else { resolve(); diff --git a/packages/types/package.json b/packages/types/package.json index 063eb0e13657..7fa60e72ab48 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -49,25 +49,17 @@ "targets": { "copy-ast-spec": { "dependsOn": [ - { - "target": "build", - "projects": "dependencies" - } + "^build" ], "outputs": [ "{projectRoot}/src/generated" - ] + ], + "cache": true }, "build": { "dependsOn": [ - { - "target": "build", - "projects": "dependencies" - }, - { - "target": "copy-ast-spec", - "projects": "self" - } + "^build", + "copy-ast-spec" ] } } From 7c673a146d26d4360a25638f901a3c329bcca8c4 Mon Sep 17 00:00:00 2001 From: "typescript-eslint[bot]" Date: Mon, 15 Jan 2024 17:15:19 +0000 Subject: [PATCH 13/13] chore(release): publish 6.19.0 --- CHANGELOG.md | 29 +++++ packages/ast-spec/CHANGELOG.md | 20 +++ packages/ast-spec/package.json | 2 +- packages/eslint-plugin-internal/CHANGELOG.md | 6 + packages/eslint-plugin-internal/package.json | 10 +- packages/eslint-plugin-tslint/CHANGELOG.md | 6 + packages/eslint-plugin-tslint/package.json | 6 +- packages/eslint-plugin/CHANGELOG.md | 31 +++++ packages/eslint-plugin/package.json | 14 +-- packages/integration-tests/CHANGELOG.md | 6 + packages/integration-tests/package.json | 2 +- packages/parser/CHANGELOG.md | 6 + packages/parser/package.json | 10 +- packages/repo-tools/CHANGELOG.md | 6 + packages/repo-tools/package.json | 2 +- .../CHANGELOG.md | 6 + .../package.json | 6 +- packages/rule-tester/CHANGELOG.md | 6 + packages/rule-tester/package.json | 8 +- packages/scope-manager/CHANGELOG.md | 6 + packages/scope-manager/package.json | 8 +- packages/type-utils/CHANGELOG.md | 25 ++++ packages/type-utils/package.json | 8 +- packages/types/CHANGELOG.md | 6 + packages/types/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 24 ++++ packages/typescript-estree/package.json | 6 +- packages/utils/CHANGELOG.md | 6 + packages/utils/package.json | 10 +- packages/visitor-keys/CHANGELOG.md | 6 + packages/visitor-keys/package.json | 4 +- packages/website-eslint/CHANGELOG.md | 6 + packages/website-eslint/package.json | 16 +-- packages/website/CHANGELOG.md | 6 + packages/website/package.json | 12 +- yarn.lock | 114 +++++++++--------- 36 files changed, 327 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 974127de56a7..af44283abf4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 6.19.0 (2024-01-15) + + +### 🚀 Features + +- **eslint-plugin:** [prefer-promise-reject-errors] add rule ([#8011](https://github.com/typescript-eslint/typescript-eslint/pull/8011)) +- **eslint-plugin:** [no-array-delete] add new rule ([#8067](https://github.com/typescript-eslint/typescript-eslint/pull/8067)) +- **eslint-plugin:** [no-useless-template-literals] add fix suggestions ([#8065](https://github.com/typescript-eslint/typescript-eslint/pull/8065)) + +### 🩹 Fixes + +- **eslint-plugin:** [no-unnecessary-type-assertion] detect unnecessary non-null-assertion on a call expression ([#8143](https://github.com/typescript-eslint/typescript-eslint/pull/8143)) +- **eslint-plugin:** [no-unnecesary-type-assertion] treat unknown/any as nullable ([#8089](https://github.com/typescript-eslint/typescript-eslint/pull/8089)) +- **typescript-estree:** add JSDocParsingMode enum merge for typescript/lib/tsserverlibrary ([#8193](https://github.com/typescript-eslint/typescript-eslint/pull/8193)) +- **typescript-estree:** disallow `using` as the variable keyword for `for..in` loops ([#7649](https://github.com/typescript-eslint/typescript-eslint/pull/7649)) +- **typescript-estree:** fix incorrect backwards-compat augmentation in TS 5.3 ([#8181](https://github.com/typescript-eslint/typescript-eslint/pull/8181)) + +### ❤️ Thank You + +- auvred @auvred +- Brad Zacher @bradzacher +- Josh Goldberg ✨ +- Joshua Chen +- LJX @lvjiaxuan +- Steven @Solo-steven +- StyleShit @StyleShit + +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. + ## 6.18.1 (2024-01-08) diff --git a/packages/ast-spec/CHANGELOG.md b/packages/ast-spec/CHANGELOG.md index 9608d2aefe36..47d00a853303 100644 --- a/packages/ast-spec/CHANGELOG.md +++ b/packages/ast-spec/CHANGELOG.md @@ -1,3 +1,23 @@ +## 6.19.0 (2024-01-15) + + +### 🩹 Fixes + +- **typescript-estree:** disallow `using` as the variable keyword for `for..in` loops + + +### ❤️ Thank You + +- auvred +- Brad Zacher +- Josh Goldberg ✨ +- Joshua Chen +- LJX +- Steven +- StyleShit + +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. + ## 6.18.1 (2024-01-08) diff --git a/packages/ast-spec/package.json b/packages/ast-spec/package.json index 3e396707de5f..1132cccd79de 100644 --- a/packages/ast-spec/package.json +++ b/packages/ast-spec/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/ast-spec", - "version": "6.18.1", + "version": "6.19.0", "description": "Complete specification for the TypeScript-ESTree AST", "private": true, "keywords": [ diff --git a/packages/eslint-plugin-internal/CHANGELOG.md b/packages/eslint-plugin-internal/CHANGELOG.md index b49de9be6a19..3633512769c8 100644 --- a/packages/eslint-plugin-internal/CHANGELOG.md +++ b/packages/eslint-plugin-internal/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for eslint-plugin-internal 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. + ## 6.18.1 (2024-01-08) This was a version bump only for eslint-plugin-internal to align it with other projects, there were no code changes. diff --git a/packages/eslint-plugin-internal/package.json b/packages/eslint-plugin-internal/package.json index e0299ce75772..33a7edcb37f6 100644 --- a/packages/eslint-plugin-internal/package.json +++ b/packages/eslint-plugin-internal/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-internal", - "version": "6.18.1", + "version": "6.19.0", "private": true, "main": "dist/index.js", "scripts": { @@ -14,10 +14,10 @@ }, "dependencies": { "@prettier/sync": "*", - "@typescript-eslint/rule-tester": "6.18.1", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/type-utils": "6.18.1", - "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/rule-tester": "6.19.0", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/type-utils": "6.19.0", + "@typescript-eslint/utils": "6.19.0", "prettier": "^3.0.3" }, "devDependencies": { diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index c98f9a8d0e66..44fcbf80c54b 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for eslint-plugin-tslint 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. + ## 6.18.1 (2024-01-08) This was a version bump only for eslint-plugin-tslint to align it with other projects, there were no code changes. diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 36ba58ba2895..ca06480892b5 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "6.18.1", + "version": "6.19.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "ESLint plugin that wraps a TSLint configuration and lints the whole source using TSLint", @@ -46,7 +46,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/utils": "6.18.1" + "@typescript-eslint/utils": "6.19.0" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0", @@ -55,7 +55,7 @@ }, "devDependencies": { "@types/lodash": "*", - "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/parser": "6.19.0", "jest": "29.7.0", "prettier": "^3.0.3", "rimraf": "*" diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 88fb389496c9..64a4e1dcd452 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -1,3 +1,34 @@ +## 6.19.0 (2024-01-15) + + +### 🚀 Features + +- **eslint-plugin:** [prefer-promise-reject-errors] add rule + +- **eslint-plugin:** [no-array-delete] add new rule + +- **eslint-plugin:** [no-useless-template-literals] add fix suggestions + + +### 🩹 Fixes + +- **eslint-plugin:** [no-unnecessary-type-assertion] detect unnecessary non-null-assertion on a call expression + +- **eslint-plugin:** [no-unnecesary-type-assertion] treat unknown/any as nullable + + +### ❤️ Thank You + +- auvred +- Brad Zacher +- Josh Goldberg ✨ +- Joshua Chen +- LJX +- Steven +- StyleShit + +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. + ## 6.18.1 (2024-01-08) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 07733150bbdf..2066c7e99c29 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "6.18.1", + "version": "6.19.0", "description": "TypeScript plugin for ESLint", "files": [ "dist", @@ -57,10 +57,10 @@ }, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/type-utils": "6.18.1", - "@typescript-eslint/utils": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/type-utils": "6.19.0", + "@typescript-eslint/utils": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -73,8 +73,8 @@ "@types/debug": "*", "@types/marked": "*", "@types/natural-compare": "*", - "@typescript-eslint/rule-schema-to-typescript-types": "6.18.1", - "@typescript-eslint/rule-tester": "6.18.1", + "@typescript-eslint/rule-schema-to-typescript-types": "6.19.0", + "@typescript-eslint/rule-tester": "6.19.0", "ajv": "^6.12.6", "chalk": "^5.3.0", "cross-fetch": "*", diff --git a/packages/integration-tests/CHANGELOG.md b/packages/integration-tests/CHANGELOG.md index 01956c239599..d254a11713de 100644 --- a/packages/integration-tests/CHANGELOG.md +++ b/packages/integration-tests/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for integration-tests 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. + ## 6.18.1 (2024-01-08) This was a version bump only for integration-tests to align it with other projects, there were no code changes. diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d917a1b7d0c5..cc57c27bd0c6 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/integration-tests", - "version": "6.18.1", + "version": "6.19.0", "private": true, "scripts": { "format": "prettier --write \"./**/*.{ts,mts,cts,tsx,js,mjs,cjs,jsx,json,md,css}\" --ignore-path ../../.prettierignore", diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 49608f459c04..e5fbedc0de30 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +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. + ## 6.18.1 (2024-01-08) 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 48005cd3604d..3f314a9c0a3f 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "6.18.1", + "version": "6.19.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "files": [ "dist", @@ -51,10 +51,10 @@ "eslint": "^7.0.0 || ^8.0.0" }, "dependencies": { - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", "debug": "^4.3.4" }, "devDependencies": { diff --git a/packages/repo-tools/CHANGELOG.md b/packages/repo-tools/CHANGELOG.md index d8f38af87fd4..a3a213849d57 100644 --- a/packages/repo-tools/CHANGELOG.md +++ b/packages/repo-tools/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for repo-tools 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. + ## 6.18.1 (2024-01-08) This was a version bump only for repo-tools to align it with other projects, there were no code changes. diff --git a/packages/repo-tools/package.json b/packages/repo-tools/package.json index 593a072fbb2c..527094065401 100644 --- a/packages/repo-tools/package.json +++ b/packages/repo-tools/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/repo-tools", - "version": "6.18.1", + "version": "6.19.0", "private": true, "scripts": { "//": "NOTE: intentionally no build step in this package", diff --git a/packages/rule-schema-to-typescript-types/CHANGELOG.md b/packages/rule-schema-to-typescript-types/CHANGELOG.md index 7c442df6eefd..663193236149 100644 --- a/packages/rule-schema-to-typescript-types/CHANGELOG.md +++ b/packages/rule-schema-to-typescript-types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +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. + ## 6.18.1 (2024-01-08) 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 bf42b1752a85..2004f67e90de 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": "6.18.1", + "version": "6.19.0", "private": true, "type": "commonjs", "exports": { @@ -34,8 +34,8 @@ }, "dependencies": { "@prettier/sync": "*", - "@typescript-eslint/type-utils": "6.18.1", - "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/type-utils": "6.19.0", + "@typescript-eslint/utils": "6.19.0", "natural-compare": "^1.4.0", "prettier": "^3.0.3" }, diff --git a/packages/rule-tester/CHANGELOG.md b/packages/rule-tester/CHANGELOG.md index 83ba6ce3089e..2c5785181b72 100644 --- a/packages/rule-tester/CHANGELOG.md +++ b/packages/rule-tester/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +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. + ## 6.18.1 (2024-01-08) 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 f9f2ba2ffdb3..fc613092dbd2 100644 --- a/packages/rule-tester/package.json +++ b/packages/rule-tester/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/rule-tester", - "version": "6.18.1", + "version": "6.19.0", "description": "Tooling to test ESLint rules", "files": [ "dist", @@ -47,8 +47,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": "6.18.1", - "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/utils": "6.19.0", "ajv": "^6.10.0", "lodash.merge": "4.6.2", "semver": "^7.5.4" @@ -59,7 +59,7 @@ }, "devDependencies": { "@types/lodash.merge": "4.6.9", - "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/parser": "6.19.0", "chai": "^4.3.7", "mocha": "^10.0.0", "sinon": "^16.0.0", diff --git a/packages/scope-manager/CHANGELOG.md b/packages/scope-manager/CHANGELOG.md index 894664c687c0..4cf6f4348675 100644 --- a/packages/scope-manager/CHANGELOG.md +++ b/packages/scope-manager/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for scope-manager 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. + ## 6.18.1 (2024-01-08) 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 edd3869831f6..d80e065def42 100644 --- a/packages/scope-manager/package.json +++ b/packages/scope-manager/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/scope-manager", - "version": "6.18.1", + "version": "6.19.0", "description": "TypeScript scope analyser for ESLint", "files": [ "dist", @@ -44,13 +44,13 @@ "typecheck": "npx nx typecheck" }, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1" + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0" }, "devDependencies": { "@prettier/sync": "*", "@types/glob": "*", - "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/typescript-estree": "6.19.0", "glob": "*", "jest-specific-snapshot": "*", "make-dir": "*", diff --git a/packages/type-utils/CHANGELOG.md b/packages/type-utils/CHANGELOG.md index 0f5e135859a1..f90bb7b61fc6 100644 --- a/packages/type-utils/CHANGELOG.md +++ b/packages/type-utils/CHANGELOG.md @@ -1,3 +1,28 @@ +## 6.19.0 (2024-01-15) + + +### 🚀 Features + +- **eslint-plugin:** [prefer-promise-reject-errors] add rule + + +### 🩹 Fixes + +- **eslint-plugin:** [no-unnecesary-type-assertion] treat unknown/any as nullable + + +### ❤️ Thank You + +- auvred +- Brad Zacher +- Josh Goldberg ✨ +- Joshua Chen +- LJX +- Steven +- StyleShit + +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. + ## 6.18.1 (2024-01-08) This was a version bump only for type-utils to align it with other projects, there were no code changes. diff --git a/packages/type-utils/package.json b/packages/type-utils/package.json index 6548eeb9c8a7..d3635aabf5d6 100644 --- a/packages/type-utils/package.json +++ b/packages/type-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/type-utils", - "version": "6.18.1", + "version": "6.19.0", "description": "Type utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -45,13 +45,13 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/utils": "6.19.0", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, "devDependencies": { - "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/parser": "6.19.0", "ajv": "^6.10.0", "downlevel-dts": "*", "jest": "29.7.0", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 37f72e1df92d..ee88f19fed34 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for 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. + ## 6.18.1 (2024-01-08) This was a version bump only for types to align it with other projects, there were no code changes. diff --git a/packages/types/package.json b/packages/types/package.json index 7fa60e72ab48..e6484f0156ef 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/types", - "version": "6.18.1", + "version": "6.19.0", "description": "Types for the TypeScript-ESTree AST spec", "files": [ "dist", diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 059d6a3f5b75..f01e5284b199 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -1,3 +1,27 @@ +## 6.19.0 (2024-01-15) + + +### 🩹 Fixes + +- **typescript-estree:** add JSDocParsingMode enum merge for typescript/lib/tsserverlibrary + +- **typescript-estree:** disallow `using` as the variable keyword for `for..in` loops + +- **typescript-estree:** fix incorrect backwards-compat augmentation in TS 5.3 + + +### ❤️ Thank You + +- auvred +- Brad Zacher +- Josh Goldberg ✨ +- Joshua Chen +- LJX +- Steven +- StyleShit + +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. + ## 6.18.1 (2024-01-08) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 1c476dcaa0cf..ffa4a4049379 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "6.18.1", + "version": "6.19.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "files": [ "dist", @@ -52,8 +52,8 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md index 52ae999b587d..d47292d16a8a 100644 --- a/packages/utils/CHANGELOG.md +++ b/packages/utils/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for 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. + ## 6.18.1 (2024-01-08) diff --git a/packages/utils/package.json b/packages/utils/package.json index 7364a438ba3a..e6fd7c529377 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/utils", - "version": "6.18.1", + "version": "6.19.0", "description": "Utilities for working with TypeScript + ESLint together", "files": [ "dist", @@ -68,16 +68,16 @@ "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", "semver": "^7.5.4" }, "peerDependencies": { "eslint": "^7.0.0 || ^8.0.0" }, "devDependencies": { - "@typescript-eslint/parser": "6.18.1", + "@typescript-eslint/parser": "6.19.0", "downlevel-dts": "*", "jest": "29.7.0", "prettier": "^3.0.3", diff --git a/packages/visitor-keys/CHANGELOG.md b/packages/visitor-keys/CHANGELOG.md index 00bf29bb06c1..720a04734ade 100644 --- a/packages/visitor-keys/CHANGELOG.md +++ b/packages/visitor-keys/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +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. + ## 6.18.1 (2024-01-08) 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 db2f5a50d126..b6444d5107e6 100644 --- a/packages/visitor-keys/package.json +++ b/packages/visitor-keys/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/visitor-keys", - "version": "6.18.1", + "version": "6.19.0", "description": "Visitor keys used to help traverse the TypeScript-ESTree AST", "files": [ "dist", @@ -45,7 +45,7 @@ "typecheck": "tsc -p tsconfig.json --noEmit" }, "dependencies": { - "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/types": "6.19.0", "eslint-visitor-keys": "^3.4.1" }, "devDependencies": { diff --git a/packages/website-eslint/CHANGELOG.md b/packages/website-eslint/CHANGELOG.md index 1009bd6da543..eea73823bbc9 100644 --- a/packages/website-eslint/CHANGELOG.md +++ b/packages/website-eslint/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for website-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. + ## 6.18.1 (2024-01-08) This was a version bump only for website-eslint to align it with other projects, there were no code changes. diff --git a/packages/website-eslint/package.json b/packages/website-eslint/package.json index f0049f1b9905..71b4e13a5fbc 100644 --- a/packages/website-eslint/package.json +++ b/packages/website-eslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/website-eslint", - "version": "6.18.1", + "version": "6.19.0", "private": true, "description": "ESLint which works in browsers.", "files": [ @@ -23,16 +23,16 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/types": "6.18.1", - "@typescript-eslint/utils": "6.18.1" + "@typescript-eslint/types": "6.19.0", + "@typescript-eslint/utils": "6.19.0" }, "devDependencies": { "@eslint/js": "8.56.0", - "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/parser": "6.18.1", - "@typescript-eslint/scope-manager": "6.18.1", - "@typescript-eslint/typescript-estree": "6.18.1", - "@typescript-eslint/visitor-keys": "6.18.1", + "@typescript-eslint/eslint-plugin": "6.19.0", + "@typescript-eslint/parser": "6.19.0", + "@typescript-eslint/scope-manager": "6.19.0", + "@typescript-eslint/typescript-estree": "6.19.0", + "@typescript-eslint/visitor-keys": "6.19.0", "esbuild": "~0.19.0", "eslint": "*", "esquery": "*", diff --git a/packages/website/CHANGELOG.md b/packages/website/CHANGELOG.md index 71f7e0e50972..c647d2d15e38 100644 --- a/packages/website/CHANGELOG.md +++ b/packages/website/CHANGELOG.md @@ -1,3 +1,9 @@ +## 6.19.0 (2024-01-15) + +This was a version bump only for website 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. + ## 6.18.1 (2024-01-08) This was a version bump only for website to align it with other projects, there were no code changes. diff --git a/packages/website/package.json b/packages/website/package.json index bd4b65fe476a..cf6ca13d4a4e 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -1,6 +1,6 @@ { "name": "website", - "version": "6.18.1", + "version": "6.19.0", "private": true, "scripts": { "build": "docusaurus build", @@ -24,8 +24,8 @@ "@docusaurus/theme-common": "~2.4.1", "@mdx-js/react": "1.6.22", "@prettier/sync": "*", - "@typescript-eslint/parser": "6.18.1", - "@typescript-eslint/website-eslint": "6.18.1", + "@typescript-eslint/parser": "6.19.0", + "@typescript-eslint/website-eslint": "6.19.0", "clsx": "^2.0.0", "eslint": "*", "json-schema": "^0.4.0", @@ -50,9 +50,9 @@ "@types/react": "*", "@types/react-helmet": "^6.1.6", "@types/react-router-dom": "^5.3.3", - "@typescript-eslint/eslint-plugin": "6.18.1", - "@typescript-eslint/rule-schema-to-typescript-types": "6.18.1", - "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/eslint-plugin": "6.19.0", + "@typescript-eslint/rule-schema-to-typescript-types": "6.19.0", + "@typescript-eslint/types": "6.19.0", "copy-webpack-plugin": "^11.0.0", "cross-fetch": "*", "globby": "^11.1.0", diff --git a/yarn.lock b/yarn.lock index d2167e10ade3..fc6417562fe4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5221,10 +5221,10 @@ __metadata: resolution: "@typescript-eslint/eslint-plugin-internal@workspace:packages/eslint-plugin-internal" dependencies: "@prettier/sync": "*" - "@typescript-eslint/rule-tester": 6.18.1 - "@typescript-eslint/scope-manager": 6.18.1 - "@typescript-eslint/type-utils": 6.18.1 - "@typescript-eslint/utils": 6.18.1 + "@typescript-eslint/rule-tester": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/type-utils": 6.19.0 + "@typescript-eslint/utils": 6.19.0 jest: 29.7.0 prettier: ^3.0.3 rimraf: "*" @@ -5236,8 +5236,8 @@ __metadata: resolution: "@typescript-eslint/eslint-plugin-tslint@workspace:packages/eslint-plugin-tslint" dependencies: "@types/lodash": "*" - "@typescript-eslint/parser": 6.18.1 - "@typescript-eslint/utils": 6.18.1 + "@typescript-eslint/parser": 6.19.0 + "@typescript-eslint/utils": 6.19.0 jest: 29.7.0 prettier: ^3.0.3 rimraf: "*" @@ -5248,7 +5248,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/eslint-plugin@6.18.1, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": +"@typescript-eslint/eslint-plugin@6.19.0, @typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin": version: 0.0.0-use.local resolution: "@typescript-eslint/eslint-plugin@workspace:packages/eslint-plugin" dependencies: @@ -5257,12 +5257,12 @@ __metadata: "@types/debug": "*" "@types/marked": "*" "@types/natural-compare": "*" - "@typescript-eslint/rule-schema-to-typescript-types": 6.18.1 - "@typescript-eslint/rule-tester": 6.18.1 - "@typescript-eslint/scope-manager": 6.18.1 - "@typescript-eslint/type-utils": 6.18.1 - "@typescript-eslint/utils": 6.18.1 - "@typescript-eslint/visitor-keys": 6.18.1 + "@typescript-eslint/rule-schema-to-typescript-types": 6.19.0 + "@typescript-eslint/rule-tester": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/type-utils": 6.19.0 + "@typescript-eslint/utils": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 ajv: ^6.12.6 chalk: ^5.3.0 cross-fetch: "*" @@ -5301,15 +5301,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/parser@6.18.1, @typescript-eslint/parser@workspace:packages/parser": +"@typescript-eslint/parser@6.19.0, @typescript-eslint/parser@workspace:packages/parser": version: 0.0.0-use.local resolution: "@typescript-eslint/parser@workspace:packages/parser" dependencies: "@types/glob": "*" - "@typescript-eslint/scope-manager": 6.18.1 - "@typescript-eslint/types": 6.18.1 - "@typescript-eslint/typescript-estree": 6.18.1 - "@typescript-eslint/visitor-keys": 6.18.1 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 debug: ^4.3.4 downlevel-dts: "*" glob: "*" @@ -5341,26 +5341,26 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/rule-schema-to-typescript-types@6.18.1, @typescript-eslint/rule-schema-to-typescript-types@workspace:packages/rule-schema-to-typescript-types": +"@typescript-eslint/rule-schema-to-typescript-types@6.19.0, @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: "@prettier/sync": "*" - "@typescript-eslint/type-utils": 6.18.1 - "@typescript-eslint/utils": 6.18.1 + "@typescript-eslint/type-utils": 6.19.0 + "@typescript-eslint/utils": 6.19.0 natural-compare: ^1.4.0 prettier: ^3.0.3 languageName: unknown linkType: soft -"@typescript-eslint/rule-tester@6.18.1, @typescript-eslint/rule-tester@workspace:packages/rule-tester": +"@typescript-eslint/rule-tester@6.19.0, @typescript-eslint/rule-tester@workspace:packages/rule-tester": version: 0.0.0-use.local resolution: "@typescript-eslint/rule-tester@workspace:packages/rule-tester" dependencies: "@types/lodash.merge": 4.6.9 - "@typescript-eslint/parser": 6.18.1 - "@typescript-eslint/typescript-estree": 6.18.1 - "@typescript-eslint/utils": 6.18.1 + "@typescript-eslint/parser": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/utils": 6.19.0 ajv: ^6.10.0 chai: ^4.3.7 lodash.merge: 4.6.2 @@ -5374,15 +5374,15 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/scope-manager@6.18.1, @typescript-eslint/scope-manager@workspace:packages/scope-manager": +"@typescript-eslint/scope-manager@6.19.0, @typescript-eslint/scope-manager@workspace:packages/scope-manager": version: 0.0.0-use.local resolution: "@typescript-eslint/scope-manager@workspace:packages/scope-manager" dependencies: "@prettier/sync": "*" "@types/glob": "*" - "@typescript-eslint/types": 6.18.1 - "@typescript-eslint/typescript-estree": 6.18.1 - "@typescript-eslint/visitor-keys": 6.18.1 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 glob: "*" jest-specific-snapshot: "*" make-dir: "*" @@ -5401,13 +5401,13 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@6.18.1, @typescript-eslint/type-utils@workspace:packages/type-utils": +"@typescript-eslint/type-utils@6.19.0, @typescript-eslint/type-utils@workspace:packages/type-utils": version: 0.0.0-use.local resolution: "@typescript-eslint/type-utils@workspace:packages/type-utils" dependencies: - "@typescript-eslint/parser": 6.18.1 - "@typescript-eslint/typescript-estree": 6.18.1 - "@typescript-eslint/utils": 6.18.1 + "@typescript-eslint/parser": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/utils": 6.19.0 ajv: ^6.10.0 debug: ^4.3.4 downlevel-dts: "*" @@ -5424,7 +5424,7 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/types@6.18.1, @typescript-eslint/types@workspace:packages/types": +"@typescript-eslint/types@6.19.0, @typescript-eslint/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@typescript-eslint/types@workspace:packages/types" dependencies: @@ -5515,14 +5515,14 @@ __metadata: languageName: unknown linkType: soft -"@typescript-eslint/typescript-estree@6.18.1, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": +"@typescript-eslint/typescript-estree@6.19.0, @typescript-eslint/typescript-estree@workspace:packages/typescript-estree": version: 0.0.0-use.local resolution: "@typescript-eslint/typescript-estree@workspace:packages/typescript-estree" dependencies: "@babel/code-frame": "*" "@babel/parser": "*" - "@typescript-eslint/types": 6.18.1 - "@typescript-eslint/visitor-keys": 6.18.1 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 debug: ^4.3.4 glob: "*" globby: ^11.1.0 @@ -5561,17 +5561,17 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@6.18.1, @typescript-eslint/utils@^6.0.0, @typescript-eslint/utils@workspace:packages/utils": +"@typescript-eslint/utils@6.19.0, @typescript-eslint/utils@^6.0.0, @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 "@types/json-schema": ^7.0.12 "@types/semver": ^7.5.0 - "@typescript-eslint/parser": 6.18.1 - "@typescript-eslint/scope-manager": 6.18.1 - "@typescript-eslint/types": 6.18.1 - "@typescript-eslint/typescript-estree": 6.18.1 + "@typescript-eslint/parser": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 downlevel-dts: "*" jest: 29.7.0 prettier: ^3.0.3 @@ -5601,12 +5601,12 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@6.18.1, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": +"@typescript-eslint/visitor-keys@6.19.0, @typescript-eslint/visitor-keys@workspace:packages/visitor-keys": version: 0.0.0-use.local resolution: "@typescript-eslint/visitor-keys@workspace:packages/visitor-keys" dependencies: "@types/eslint-visitor-keys": "*" - "@typescript-eslint/types": 6.18.1 + "@typescript-eslint/types": 6.19.0 downlevel-dts: "*" eslint-visitor-keys: ^3.4.1 jest: 29.7.0 @@ -5626,18 +5626,18 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/website-eslint@6.18.1, @typescript-eslint/website-eslint@workspace:packages/website-eslint": +"@typescript-eslint/website-eslint@6.19.0, @typescript-eslint/website-eslint@workspace:packages/website-eslint": version: 0.0.0-use.local resolution: "@typescript-eslint/website-eslint@workspace:packages/website-eslint" dependencies: "@eslint/js": 8.56.0 - "@typescript-eslint/eslint-plugin": 6.18.1 - "@typescript-eslint/parser": 6.18.1 - "@typescript-eslint/scope-manager": 6.18.1 - "@typescript-eslint/types": 6.18.1 - "@typescript-eslint/typescript-estree": 6.18.1 - "@typescript-eslint/utils": 6.18.1 - "@typescript-eslint/visitor-keys": 6.18.1 + "@typescript-eslint/eslint-plugin": 6.19.0 + "@typescript-eslint/parser": 6.19.0 + "@typescript-eslint/scope-manager": 6.19.0 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/typescript-estree": 6.19.0 + "@typescript-eslint/utils": 6.19.0 + "@typescript-eslint/visitor-keys": 6.19.0 esbuild: ~0.19.0 eslint: "*" esquery: "*" @@ -18863,11 +18863,11 @@ __metadata: "@types/react": "*" "@types/react-helmet": ^6.1.6 "@types/react-router-dom": ^5.3.3 - "@typescript-eslint/eslint-plugin": 6.18.1 - "@typescript-eslint/parser": 6.18.1 - "@typescript-eslint/rule-schema-to-typescript-types": 6.18.1 - "@typescript-eslint/types": 6.18.1 - "@typescript-eslint/website-eslint": 6.18.1 + "@typescript-eslint/eslint-plugin": 6.19.0 + "@typescript-eslint/parser": 6.19.0 + "@typescript-eslint/rule-schema-to-typescript-types": 6.19.0 + "@typescript-eslint/types": 6.19.0 + "@typescript-eslint/website-eslint": 6.19.0 clsx: ^2.0.0 copy-webpack-plugin: ^11.0.0 cross-fetch: "*" pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy