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) diff --git a/packages/eslint-plugin/docs/rules/only-throw-error.mdx b/packages/eslint-plugin/docs/rules/only-throw-error.mdx index 8fffde428fee..109b1763ebf1 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. + */ + allow?: ( + | { + 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,8 +135,9 @@ interface Options { } const defaultOptions: Options = { - 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 0ebd6048bee3..87f6a1e34152 100644 --- a/packages/eslint-plugin/src/rules/only-throw-error.ts +++ b/packages/eslint-plugin/src/rules/only-throw-error.ts @@ -3,18 +3,23 @@ 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, + typeMatchesSomeSpecifier, + typeOrValueSpecifiersSchema, } from '../util'; type MessageIds = 'object' | 'undef'; type Options = [ { + allow?: TypeOrValueSpecifier[]; allowThrowingAny?: boolean; allowThrowingUnknown?: boolean; }, @@ -39,6 +44,10 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { + allow: { + ...typeOrValueSpecifiersSchema, + description: 'Type specifiers that can be thrown.', + }, allowThrowingAny: { type: 'boolean', description: @@ -55,13 +64,14 @@ export default createRule({ }, defaultOptions: [ { + allow: [], allowThrowingAny: true, allowThrowingUnknown: true, }, ], create(context, [options]) { const services = getParserServices(context); - + const allow = options.allow; function checkThrowArgument(node: TSESTree.Node): void { if ( node.type === AST_NODE_TYPES.AwaitExpression || @@ -72,6 +82,10 @@ export default createRule({ const type = services.getTypeAtLocation(node); + if (typeMatchesSomeSpecifier(type, allow, 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..b38f6885bf80 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/errors.ts @@ -0,0 +1,6 @@ +// @ts-ignore +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..c7d52c6a1e1c 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,56 @@ function fun(t: T): void { throw t; } `, + { + code: ` +throw undefined; + `, + options: [ + { + allow: [{ from: 'lib', name: 'undefined' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, + { + code: ` +class CustomError implements Error {} +throw new CustomError(); + `, + options: [ + { + allow: [{ from: 'file', name: 'CustomError' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, + { + code: ` +throw new Map(); + `, + options: [ + { + allow: [{ from: 'lib', name: 'Map' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, + { + code: ` + import { createError } from 'errors'; + throw createError(); + `, + options: [ + { + allow: [{ from: 'package', name: 'ErrorLike', package: 'errors' }], + allowThrowingAny: false, + allowThrowingUnknown: false, + }, + ], + }, ], invalid: [ { @@ -485,5 +535,23 @@ function fun(t: T): void { }, ], }, + { + code: ` +class UnknownError implements Error {} +throw new UnknownError(); + `, + errors: [ + { + messageId: 'object', + }, + ], + options: [ + { + 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 05b6ae4af6b0..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,6 +8,101 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { + "allow": { + "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. */ + allow?: ( + | { + 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\`. */