diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx index e3f047e6e4f0..c3d051151d6f 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -176,6 +176,44 @@ returnsSafePromise(); +### `allowForKnownSafeCalls` + +This option allows marking specific functions as "safe" to be called to create floating Promises. +For example, you may need to do this in the case of libraries whose APIs may be called without handling the resultant Promises. + +This option takes the same array format as [`allowForKnownSafePromises`](#allowForKnownSafePromises). + +Examples of code for this rule with: + +```json +{ + "allowForKnownSafeCalls": [ + { "from": "file", "name": "safe", "path": "input.ts" } + ] +} +``` + + + + +```ts option='{"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]}' +declare function unsafe(...args: unknown[]): Promise; + +unsafe('...', () => {}); +``` + + + + +```ts option='{"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]}' skipValidation +declare function safe(...args: unknown[]): Promise; + +safe('...', () => {}); +``` + + + + ## When Not To Use It This rule can be difficult to enable on large existing projects that set up many floating Promises. diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 1ae5e602ae0e..ad25d241be55 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -19,6 +19,7 @@ type Options = [ ignoreVoid?: boolean; ignoreIIFE?: boolean; allowForKnownSafePromises?: TypeOrValueSpecifier[]; + allowForKnownSafeCalls?: TypeOrValueSpecifier[]; }, ]; @@ -85,6 +86,7 @@ export default createRule({ type: 'boolean', }, allowForKnownSafePromises: readonlynessOptionsSchema.properties.allow, + allowForKnownSafeCalls: readonlynessOptionsSchema.properties.allow, }, additionalProperties: false, }, @@ -96,6 +98,7 @@ export default createRule({ ignoreVoid: true, ignoreIIFE: false, allowForKnownSafePromises: readonlynessOptionsDefaults.allow, + allowForKnownSafeCalls: readonlynessOptionsDefaults.allow, }, ], @@ -103,8 +106,10 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); // TODO: #5439 - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + /* eslint-disable @typescript-eslint/no-non-null-assertion */ const allowForKnownSafePromises = options.allowForKnownSafePromises!; + const allowForKnownSafeCalls = options.allowForKnownSafeCalls!; + /* eslint-enable @typescript-eslint/no-non-null-assertion */ return { ExpressionStatement(node): void { @@ -118,6 +123,10 @@ export default createRule({ expression = expression.expression; } + if (isKnownSafePromiseReturn(expression)) { + return; + } + const { isUnhandled, nonFunctionHandler, promiseArray } = isUnhandledPromise(checker, expression); @@ -197,6 +206,18 @@ export default createRule({ }, }; + function isKnownSafePromiseReturn(node: TSESTree.Node): boolean { + if (node.type !== AST_NODE_TYPES.CallExpression) { + return false; + } + + const type = services.getTypeAtLocation(node.callee); + + return allowForKnownSafeCalls.some(allowedType => + typeMatchesSpecifier(type, allowedType, services.program), + ); + } + function isHigherPrecedenceThanUnary(node: ts.Node): boolean { const operator = ts.isBinaryExpression(node) ? node.operatorToken.kind diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot index 44e303fc3131..e1f38e71fe3e 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-floating-promises.shot @@ -108,3 +108,25 @@ function returnsSafePromise(): SafePromise { returnsSafePromise(); " `; + +exports[`Validating rule docs no-floating-promises.mdx code examples ESLint output 7`] = ` +"Incorrect +Options: {"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]} + +declare function unsafe(...args: unknown[]): Promise; + +unsafe('...', () => {}); +~~~~~~~~~~~~~~~~~~~~~~~~ Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the \`void\` operator. +" +`; + +exports[`Validating rule docs no-floating-promises.mdx code examples ESLint output 8`] = ` +"Correct +Options: {"allowForKnownSafeCalls":[{"from":"file","name":"safe","path":"input.ts"}]} + +declare function safe(...args: unknown[]): Promise; + +safe('...', () => {}); +~~~~~~~~~~~~~~~~~~~~~~ Promises must be awaited, end with a call to .catch, end with a call to .then with a rejection handler or be explicitly marked as ignored with the \`void\` operator. +" +`; diff --git a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts index e6087e512265..d5d62eeddf56 100644 --- a/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts +++ b/packages/eslint-plugin/tests/rules/no-floating-promises.test.ts @@ -698,6 +698,27 @@ myTag\`abc\`; { allowForKnownSafePromises: [{ from: 'file', name: 'SafePromise' }] }, ], }, + { + code: ` + declare function it(...args: unknown[]): Promise; + + it('...', () => {}); + `, + options: [ + { + allowForKnownSafeCalls: [ + { + from: 'file', + name: 'it', + // https://github.com/typescript-eslint/typescript-eslint/pull/9234/files#r1626465054 + path: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE + ? 'file.ts' + : 'tests/fixtures/file.ts', + }, + ], + }, + ], + }, { code: ` declare const myTag: (strings: TemplateStringsArray) => Promise; @@ -2181,5 +2202,71 @@ myTag\`abc\`; options: [{ allowForKnownSafePromises: [{ from: 'file', name: 'Foo' }] }], errors: [{ line: 4, messageId: 'floatingVoid' }], }, + { + code: ` + declare function unsafe(...args: unknown[]): Promise; + + unsafe('...', () => {}); + `, + errors: [{ line: 4, messageId: 'floatingVoid' }], + options: [ + { + allowForKnownSafeCalls: [ + { + from: 'file', + name: 'it', + // https://github.com/typescript-eslint/typescript-eslint/pull/9234/files#r1626465054 + path: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE + ? 'file.ts' + : 'tests/fixtures/file.ts', + }, + ], + }, + ], + }, + { + code: ` + declare function it(...args: unknown[]): Promise; + + it('...', () => {}).then(() => {}); + `, + errors: [{ line: 4, messageId: 'floatingVoid' }], + options: [ + { + allowForKnownSafeCalls: [ + { + from: 'file', + name: 'it', + // https://github.com/typescript-eslint/typescript-eslint/pull/9234/files#r1626465054 + path: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE + ? 'file.ts' + : 'tests/fixtures/file.ts', + }, + ], + }, + ], + }, + { + code: ` + declare function it(...args: unknown[]): Promise; + + it('...', () => {}).finally(() => {}); + `, + errors: [{ line: 4, messageId: 'floatingVoid' }], + options: [ + { + allowForKnownSafeCalls: [ + { + from: 'file', + name: 'it', + // https://github.com/typescript-eslint/typescript-eslint/pull/9234/files#r1626465054 + path: process.env.TYPESCRIPT_ESLINT_PROJECT_SERVICE + ? 'file.ts' + : 'tests/fixtures/file.ts', + }, + ], + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-floating-promises.shot b/packages/eslint-plugin/tests/schema-snapshots/no-floating-promises.shot index a708c7001d5b..b73c2638b92e 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-floating-promises.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-floating-promises.shot @@ -8,6 +8,100 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { + "allowForKnownSafeCalls": { + "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" + }, "allowForKnownSafePromises": { "items": { "oneOf": [ @@ -120,6 +214,23 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos type Options = [ { + allowForKnownSafeCalls?: ( + | { + from: 'file'; + name: [string, ...string[]] | string; + path?: string; + } + | { + from: 'lib'; + name: [string, ...string[]] | string; + } + | { + from: 'package'; + name: [string, ...string[]] | string; + package: string; + } + | string + )[]; allowForKnownSafePromises?: ( | { from: 'file'; 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