From d4bdc747a0fdf17f66fadae804ab488803e11428 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 28 Oct 2024 23:35:09 +0900 Subject: [PATCH 1/5] feat(eslint-plugin): [only-throw-error] add allowThrowing option --- .../docs/rules/only-throw-error.mdx | 22 ++++++ .../src/rules/only-throw-error.ts | 17 ++++- .../eslint-plugin/tests/fixtures/errors.ts | 5 ++ .../tests/fixtures/tsconfig.json | 3 +- .../tests/rules/only-throw-error.test.ts | 70 +++++++++++++++++++ 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/errors.ts diff --git a/packages/eslint-plugin/docs/rules/only-throw-error.mdx b/packages/eslint-plugin/docs/rules/only-throw-error.mdx index 8fffde428fee..199544435c32 100644 --- a/packages/eslint-plugin/docs/rules/only-throw-error.mdx +++ b/packages/eslint-plugin/docs/rules/only-throw-error.mdx @@ -102,6 +102,27 @@ This rule adds the following options: ```ts interface Options { + /** + * Type specifiers that can be thrown. + */ + allowThrowing?: ( + | { + from: 'file'; + name: [string, ...string[]] | string; + path?: string; + } + | { + from: 'lib'; + name: [string, ...string[]] | string; + } + | { + from: 'package'; + name: [string, ...string[]] | string; + package: string; + } + | string + )[]; + /** * Whether to always allow throwing values typed as `any`. */ @@ -114,6 +135,7 @@ interface Options { } const defaultOptions: Options = { + allowThrowing: [], allowThrowingAny: false, allowThrowingUnknown: false, }; diff --git a/packages/eslint-plugin/src/rules/only-throw-error.ts b/packages/eslint-plugin/src/rules/only-throw-error.ts index 0ebd6048bee3..c1bc46864c6b 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -3,18 +3,24 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import * as ts from 'typescript'; +import type { TypeOrValueSpecifier } from '../util'; + import { createRule, getParserServices, isErrorLike, isTypeAnyType, isTypeUnknownType, + readonlynessOptionsDefaults, + readonlynessOptionsSchema, + typeMatchesSomeSpecifier, } from '../util'; type MessageIds = 'object' | 'undef'; type Options = [ { + allowThrowing?: TypeOrValueSpecifier[]; allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; }, @@ -39,6 +45,10 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { + allowThrowing: { + ...readonlynessOptionsSchema.properties.allow, + description: 'Type specifiers that can be thrown.', + }, allowThrowingAny: { type: 'boolean', description: @@ -55,13 +65,14 @@ export default createRule({ }, defaultOptions: [ { + allowThrowing: readonlynessOptionsDefaults.allow, allowThrowingAny: true, allowThrowingUnknown: true, }, ], create(context, [options]) { const services = getParserServices(context); - + const allowThrowing = options.allowThrowing; function checkThrowArgument(node: TSESTree.Node): void { if ( node.type === AST_NODE_TYPES.AwaitExpression || @@ -72,6 +83,10 @@ export default createRule({ const type = services.getTypeAtLocation(node); + if (typeMatchesSomeSpecifier(type, allowThrowing, services.program)) { + return; + } + if (type.flags & ts.TypeFlags.Undefined) { context.report({ node, messageId: 'undef' }); return; diff --git a/packages/eslint-plugin/tests/fixtures/errors.ts b/packages/eslint-plugin/tests/fixtures/errors.ts new file mode 100644 index 000000000000..63addd9d7df8 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/errors.ts @@ -0,0 +1,5 @@ +declare module 'errors' { + class ErrorLike {} + + export function createError(): ErrorLike; +} diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig.json b/packages/eslint-plugin/tests/fixtures/tsconfig.json index a0fc993b1f48..d8141e1ddfa4 100644 --- a/packages/eslint-plugin/tests/fixtures/tsconfig.json +++ b/packages/eslint-plugin/tests/fixtures/tsconfig.json @@ -14,6 +14,7 @@ "deprecated.ts", "mixed-enums-decl.ts", "react.tsx", - "var-declaration.ts" + "var-declaration.ts", + "errors.ts" ] } diff --git a/packages/eslint-plugin/tests/rules/only-throw-error.test.ts b/packages/eslint-plugin/tests/rules/only-throw-error.test.ts index b20673db84ea..533d2a1140cd 100644 --- a/packages/eslint-plugin/tests/rules/only-throw-error.test.ts +++ b/packages/eslint-plugin/tests/rules/only-throw-error.test.ts @@ -139,6 +139,58 @@ function fun(t: T): void { throw t; } `, + { + code: ` +throw undefined; + `, + options: [ + { + allowThrowing: [{ from: 'lib', name: 'undefined' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, + { + code: ` +class CustomError implements Error {} +throw new CustomError(); + `, + options: [ + { + allowThrowing: [{ from: 'file', name: 'CustomError' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, + { + code: ` +throw new Map(); + `, + options: [ + { + allowThrowing: [{ from: 'lib', name: 'Map' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, + { + code: ` + import { createError } from 'errors'; + throw createError(); + `, + options: [ + { + allowThrowing: [ + { from: 'package', name: 'ErrorLike', package: 'errors' }, + ], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, ], invalid: [ { @@ -485,5 +537,23 @@ function fun(t: T): void { }, ], }, + { + code: ` +class UnknownError implements Error {} +throw new UnknownError(); + `, + errors: [ + { + messageId: 'object', + }, + ], + options: [ + { + allowThrowing: [{ from: 'file', name: 'CustomError' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, ], }); From 124faa97d974bf77fba2af73511539d39d77891e Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Mon, 28 Oct 2024 23:46:08 +0900 Subject: [PATCH 2/5] ts ignore --- packages/eslint-plugin/tests/fixtures/errors.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/tests/fixtures/errors.ts b/packages/eslint-plugin/tests/fixtures/errors.ts index 63addd9d7df8..b38f6885bf80 100644 --- a/packages/eslint-plugin/tests/fixtures/errors.ts +++ b/packages/eslint-plugin/tests/fixtures/errors.ts @@ -1,3 +1,4 @@ +// @ts-ignore declare module 'errors' { class ErrorLike {} From 7889124c11147f90fe1e8063a640dcc5b3d2dc72 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Tue, 29 Oct 2024 00:02:53 +0900 Subject: [PATCH 3/5] Update only-throw-error.shot --- .../schema-snapshots/only-throw-error.shot | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot b/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot index 05b6ae4af6b0..aa00408329a9 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot @@ -8,6 +8,101 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { + "allowThrowing": { + "description": "Type specifiers that can be thrown.", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["file"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "path": { + "type": "string" + } + }, + "required": ["from", "name"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["lib"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + } + }, + "required": ["from", "name"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["package"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "package": { + "type": "string" + } + }, + "required": ["from", "name", "package"], + "type": "object" + } + ] + }, + "type": "array" + }, "allowThrowingAny": { "description": "Whether to always allow throwing values typed as \`any\`.", "type": "boolean" @@ -26,6 +121,24 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos type Options = [ { + /** Type specifiers that can be thrown. */ + allowThrowing?: ( + | { + from: 'file'; + name: [string, ...string[]] | string; + path?: string; + } + | { + from: 'lib'; + name: [string, ...string[]] | string; + } + | { + from: 'package'; + name: [string, ...string[]] | string; + package: string; + } + | string + )[]; /** Whether to always allow throwing values typed as \`any\`. */ allowThrowingAny?: boolean; /** Whether to always allow throwing values typed as \`unknown\`. */ From aff345c1150996386ea2c3a88c7a7b1e272eec90 Mon Sep 17 00:00:00 2001 From: YeonJuan Date: Wed, 30 Oct 2024 12:35:53 +0900 Subject: [PATCH 4/5] review --- .../eslint-plugin/docs/rules/only-throw-error.mdx | 8 ++++---- .../eslint-plugin/src/rules/only-throw-error.ts | 15 +++++++-------- .../tests/rules/only-throw-error.test.ts | 12 +++++------- .../tests/schema-snapshots/only-throw-error.shot | 4 ++-- 4 files changed, 18 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/only-throw-error.mdx b/packages/eslint-plugin/docs/rules/only-throw-error.mdx index 199544435c32..109b1763ebf1 100644 --- a/packages/eslint-plugin/docs/rules/only-throw-error.mdx +++ b/packages/eslint-plugin/docs/rules/only-throw-error.mdx @@ -105,7 +105,7 @@ interface Options { /** * Type specifiers that can be thrown. */ - allowThrowing?: ( + allow?: ( | { from: 'file'; name: [string, ...string[]] | string; @@ -135,9 +135,9 @@ interface Options { } const defaultOptions: Options = { - allowThrowing: [], - allowThrowingAny: false, - allowThrowingUnknown: false, + allow: [], + allowThrowingAny: true, + allowThrowingUnknown: true, }; ``` diff --git a/packages/eslint-plugin/src/rules/only-throw-error.ts b/packages/eslint-plugin/src/rules/only-throw-error.ts index c1bc46864c6b..87f6a1e34152 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -11,16 +11,15 @@ import { isErrorLike, isTypeAnyType, isTypeUnknownType, - readonlynessOptionsDefaults, - readonlynessOptionsSchema, typeMatchesSomeSpecifier, + typeOrValueSpecifiersSchema, } from '../util'; type MessageIds = 'object' | 'undef'; type Options = [ { - allowThrowing?: TypeOrValueSpecifier[]; + allow?: TypeOrValueSpecifier[]; allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; }, @@ -45,8 +44,8 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { - allowThrowing: { - ...readonlynessOptionsSchema.properties.allow, + allow: { + ...typeOrValueSpecifiersSchema, description: 'Type specifiers that can be thrown.', }, allowThrowingAny: { @@ -65,14 +64,14 @@ export default createRule({ }, defaultOptions: [ { - allowThrowing: readonlynessOptionsDefaults.allow, + allow: [], allowThrowingAny: true, allowThrowingUnknown: true, }, ], create(context, [options]) { const services = getParserServices(context); - const allowThrowing = options.allowThrowing; + const allow = options.allow; function checkThrowArgument(node: TSESTree.Node): void { if ( node.type === AST_NODE_TYPES.AwaitExpression || @@ -83,7 +82,7 @@ export default createRule({ const type = services.getTypeAtLocation(node); - if (typeMatchesSomeSpecifier(type, allowThrowing, services.program)) { + if (typeMatchesSomeSpecifier(type, allow, services.program)) { return; } diff --git a/packages/eslint-plugin/tests/rules/only-throw-error.test.ts b/packages/eslint-plugin/tests/rules/only-throw-error.test.ts index 533d2a1140cd..c7d52c6a1e1c 100644 --- a/packages/eslint-plugin/tests/rules/only-throw-error.test.ts +++ b/packages/eslint-plugin/tests/rules/only-throw-error.test.ts @@ -145,7 +145,7 @@ throw undefined; `, options: [ { - allowThrowing: [{ from: 'lib', name: 'undefined' }], + allow: [{ from: 'lib', name: 'undefined' }], allowThrowingAny: false, allowThrowingUnknown: false, }, @@ -158,7 +158,7 @@ throw new CustomError(); `, options: [ { - allowThrowing: [{ from: 'file', name: 'CustomError' }], + allow: [{ from: 'file', name: 'CustomError' }], allowThrowingAny: false, allowThrowingUnknown: false, }, @@ -170,7 +170,7 @@ throw new Map(); `, options: [ { - allowThrowing: [{ from: 'lib', name: 'Map' }], + allow: [{ from: 'lib', name: 'Map' }], allowThrowingAny: false, allowThrowingUnknown: false, }, @@ -183,9 +183,7 @@ throw new Map(); `, options: [ { - allowThrowing: [ - { from: 'package', name: 'ErrorLike', package: 'errors' }, - ], + allow: [{ from: 'package', name: 'ErrorLike', package: 'errors' }], allowThrowingAny: false, allowThrowingUnknown: false, }, @@ -549,7 +547,7 @@ throw new UnknownError(); ], options: [ { - allowThrowing: [{ from: 'file', name: 'CustomError' }], + allow: [{ from: 'file', name: 'CustomError' }], allowThrowingAny: false, allowThrowingUnknown: false, }, diff --git a/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot b/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot index aa00408329a9..c2ddc66bcc81 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/only-throw-error.shot @@ -8,7 +8,7 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { - "allowThrowing": { + "allow": { "description": "Type specifiers that can be thrown.", "items": { "oneOf": [ @@ -122,7 +122,7 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos type Options = [ { /** Type specifiers that can be thrown. */ - allowThrowing?: ( + allow?: ( | { from: 'file'; name: [string, ...string[]] | string; From fcf19d87aafda6258e38ecaa5695d23a1f61b06d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 4 Nov 2024 08:53:38 -0500 Subject: [PATCH 5/5] Add to TypeOrValueSpecifier.mdx options --- docs/packages/type-utils/TypeOrValueSpecifier.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/packages/type-utils/TypeOrValueSpecifier.mdx b/docs/packages/type-utils/TypeOrValueSpecifier.mdx index b9d76442406a..7a21d16cf26d 100644 --- a/docs/packages/type-utils/TypeOrValueSpecifier.mdx +++ b/docs/packages/type-utils/TypeOrValueSpecifier.mdx @@ -132,4 +132,5 @@ Universal string specifiers will be removed in a future major version of typescr - [`@typescript-eslint/no-floating-promises` > `allowForKnownSafeCalls`](/rules/no-floating-promises#allowforknownsafecalls) - [`@typescript-eslint/no-floating-promises` > `allowForKnownSafePromises`](/rules/no-floating-promises#allowforknownsafepromises) +- [`@typescript-eslint/only-throw-error` > `allow`](/rules/only-throw-error/#allow) - [`@typescript-eslint/prefer-readonly-parameter-types` > `allow`](/rules/prefer-readonly-parameter-types/#allow)