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';