diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx
index 3e56c1e0f092..6fc46d6cbf18 100644
--- a/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx
+++ b/packages/eslint-plugin/docs/rules/no-unsafe-call.mdx
@@ -59,6 +59,34 @@ String.raw`foo`;
+## The Unsafe `Function` Type
+
+The `Function` type is behaves almost identically to `any` when called, so this rule also disallows calling values of type `Function`.
+
+
+
+
+```ts
+const f: Function = () => {};
+f();
+```
+
+
+
+
+Note that whereas [no-unsafe-function-type](./no-unsafe-function-type.mdx) helps prevent the _creation_ of `Function` types, this rule helps prevent the unsafe _use_ of `Function` types, which may creep into your codebase without explicitly referencing the `Function` type at all.
+See, for example, the following code:
+
+```ts
+function unsafe(maybeFunction: unknown): string {
+ if (typeof maybeFunction === 'function') {
+ // TypeScript allows this, but it's completely unsound.
+ return maybeFunction('call', 'with', 'any', 'args');
+ }
+ // etc
+}
+```
+
## When Not To Use It
If your codebase has many existing `any`s or areas of unsafe code, it may be difficult to enable this rule.
diff --git a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx
index ea7b60794e4e..ee1a84395d8d 100644
--- a/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx
+++ b/packages/eslint-plugin/docs/rules/no-unsafe-function-type.mdx
@@ -60,4 +60,5 @@ You might consider using [ESLint disable comments](https://eslint.org/docs/lates
- [`no-empty-object-type`](./no-empty-object-type.mdx)
- [`no-restricted-types`](./no-restricted-types.mdx)
+- [`no-unsafe-call`](./no-unsafe-call.mdx)
- [`no-wrapper-object-types`](./no-wrapper-object-types.mdx)
diff --git a/packages/eslint-plugin/src/rules/no-unsafe-call.ts b/packages/eslint-plugin/src/rules/no-unsafe-call.ts
index 6bdec17a4427..8149f2736a15 100644
--- a/packages/eslint-plugin/src/rules/no-unsafe-call.ts
+++ b/packages/eslint-plugin/src/rules/no-unsafe-call.ts
@@ -6,6 +6,7 @@ import {
getConstrainedTypeAtLocation,
getParserServices,
getThisExpression,
+ isBuiltinSymbolLike,
isTypeAnyType,
} from '../util';
@@ -25,13 +26,13 @@ export default createRule<[], MessageIds>({
requiresTypeChecking: true,
},
messages: {
- unsafeCall: 'Unsafe call of an {{type}} typed value.',
+ unsafeCall: 'Unsafe call of a(n) {{type}} typed value.',
unsafeCallThis: [
- 'Unsafe call of an `any` typed value. `this` is typed as `any`.',
+ 'Unsafe call of a(n) {{type}} typed value. `this` is typed as {{type}}.',
'You can try to fix this by turning on the `noImplicitThis` compiler option, or adding a `this` parameter to the function.',
].join('\n'),
- unsafeNew: 'Unsafe construction of an any type value.',
- unsafeTemplateTag: 'Unsafe any typed template tag.',
+ unsafeNew: 'Unsafe construction of a(n) {{type}} typed value.',
+ unsafeTemplateTag: 'Unsafe use of a(n) {{type}} typed template tag.',
},
schema: [],
},
@@ -74,6 +75,49 @@ export default createRule<[], MessageIds>({
type: isErrorType ? '`error` type' : '`any`',
},
});
+ return;
+ }
+
+ if (isBuiltinSymbolLike(services.program, type, 'Function')) {
+ // this also matches subtypes of `Function`, like `interface Foo extends Function {}`.
+ //
+ // For weird TS reasons that I don't understand, these are
+ //
+ // safe to construct if:
+ // - they have at least one call signature _that is not void-returning_,
+ // - OR they have at least one construct signature.
+ //
+ // safe to call (including as template) if:
+ // - they have at least one call signature
+ // - OR they have at least one construct signature.
+
+ const constructSignatures = type.getConstructSignatures();
+ if (constructSignatures.length > 0) {
+ return;
+ }
+
+ const callSignatures = type.getCallSignatures();
+ if (messageId === 'unsafeNew') {
+ if (
+ callSignatures.some(
+ signature =>
+ !tsutils.isIntrinsicVoidType(signature.getReturnType()),
+ )
+ ) {
+ return;
+ }
+ } else if (callSignatures.length > 0) {
+ return;
+ }
+
+ context.report({
+ node: reportingNode,
+ messageId,
+ data: {
+ type: '`Function`',
+ },
+ });
+ return;
}
}
diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot
index bfc1388d6cf9..7b3ee29e7a93 100644
--- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot
+++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-unsafe-call.shot
@@ -7,24 +7,24 @@ declare const anyVar: any;
declare const nestedAny: { prop: any };
anyVar();
-~~~~~~ Unsafe call of an \`any\` typed value.
+~~~~~~ Unsafe call of a(n) \`any\` typed value.
anyVar.a.b();
-~~~~~~~~~~ Unsafe call of an \`any\` typed value.
+~~~~~~~~~~ Unsafe call of a(n) \`any\` typed value.
nestedAny.prop();
-~~~~~~~~~~~~~~ Unsafe call of an \`any\` typed value.
+~~~~~~~~~~~~~~ Unsafe call of a(n) \`any\` typed value.
nestedAny.prop['a']();
-~~~~~~~~~~~~~~~~~~~ Unsafe call of an \`any\` typed value.
+~~~~~~~~~~~~~~~~~~~ Unsafe call of a(n) \`any\` typed value.
new anyVar();
-~~~~~~~~~~~~ Unsafe construction of an any type value.
+~~~~~~~~~~~~ Unsafe construction of a(n) \`any\` typed value.
new nestedAny.prop();
-~~~~~~~~~~~~~~~~~~~~ Unsafe construction of an any type value.
+~~~~~~~~~~~~~~~~~~~~ Unsafe construction of a(n) \`any\` typed value.
anyVar\`foo\`;
-~~~~~~ Unsafe any typed template tag.
+~~~~~~ Unsafe use of a(n) \`any\` typed template tag.
nestedAny.prop\`foo\`;
-~~~~~~~~~~~~~~ Unsafe any typed template tag.
+~~~~~~~~~~~~~~ Unsafe use of a(n) \`any\` typed template tag.
"
`;
@@ -44,3 +44,12 @@ new Map();
String.raw\`foo\`;
"
`;
+
+exports[`Validating rule docs no-unsafe-call.mdx code examples ESLint output 3`] = `
+"Incorrect
+
+const f: Function = () => {};
+f();
+~ Unsafe call of a(n) \`Function\` typed value.
+"
+`;
diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts
index 1a26a4ef3d33..8baf831f96e6 100644
--- a/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unsafe-call.test.ts
@@ -44,6 +44,52 @@ function foo(x: { a?: () => void }) {
x();
}
`,
+ `
+ // create a scope since it's illegal to declare a duplicate identifier
+ // 'Function' in the global script scope.
+ {
+ type Function = () => void;
+ const notGlobalFunctionType: Function = (() => {}) as Function;
+ notGlobalFunctionType();
+ }
+ `,
+ `
+interface SurprisinglySafe extends Function {
+ (): string;
+}
+declare const safe: SurprisinglySafe;
+safe();
+ `,
+ `
+interface CallGoodConstructBad extends Function {
+ (): void;
+}
+declare const safe: CallGoodConstructBad;
+safe();
+ `,
+ `
+interface ConstructSignatureMakesSafe extends Function {
+ new (): ConstructSignatureMakesSafe;
+}
+declare const safe: ConstructSignatureMakesSafe;
+new safe();
+ `,
+ `
+interface SafeWithNonVoidCallSignature extends Function {
+ (): void;
+ (x: string): string;
+}
+declare const safe: SafeWithNonVoidCallSignature;
+safe();
+ `,
+ // Function has type FunctionConstructor, so it's not within this rule's purview
+ `
+ new Function('lol');
+ `,
+ // Function has type FunctionConstructor, so it's not within this rule's purview
+ `
+ Function('lol');
+ `,
],
invalid: [
{
@@ -251,5 +297,136 @@ value();
},
],
},
+ {
+ code: `
+const t: Function = () => {};
+t();
+ `,
+ errors: [
+ {
+ messageId: 'unsafeCall',
+ line: 3,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+const f: Function = () => {};
+f\`oo\`;
+ `,
+ errors: [
+ {
+ messageId: 'unsafeTemplateTag',
+ line: 3,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+declare const maybeFunction: unknown;
+if (typeof maybeFunction === 'function') {
+ maybeFunction('call', 'with', 'any', 'args');
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeCall',
+ line: 4,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+interface Unsafe extends Function {}
+declare const unsafe: Unsafe;
+unsafe();
+ `,
+ errors: [
+ {
+ messageId: 'unsafeCall',
+ line: 4,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+interface Unsafe extends Function {}
+declare const unsafe: Unsafe;
+unsafe\`bad\`;
+ `,
+ errors: [
+ {
+ messageId: 'unsafeTemplateTag',
+ line: 4,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+interface Unsafe extends Function {}
+declare const unsafe: Unsafe;
+new unsafe();
+ `,
+ errors: [
+ {
+ messageId: 'unsafeNew',
+ line: 4,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+interface UnsafeToConstruct extends Function {
+ (): void;
+}
+declare const unsafe: UnsafeToConstruct;
+new unsafe();
+ `,
+ errors: [
+ {
+ messageId: 'unsafeNew',
+ line: 6,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
+ {
+ code: `
+interface StillUnsafe extends Function {
+ property: string;
+}
+declare const unsafe: StillUnsafe;
+unsafe();
+ `,
+ errors: [
+ {
+ messageId: 'unsafeCall',
+ line: 6,
+ data: {
+ type: '`Function`',
+ },
+ },
+ ],
+ },
],
});
diff --git a/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts
index 75c116a66aa5..ae5b96d1ad51 100644
--- a/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts
+++ b/packages/eslint-plugin/tests/rules/no-unsafe-function-type.test.ts
@@ -9,8 +9,12 @@ ruleTester.run('no-unsafe-function-type', rule, {
'let value: () => void;',
'let value: (t: T) => T;',
`
- type Function = () => void;
- let value: Function;
+ // create a scope since it's illegal to declare a duplicate identifier
+ // 'Function' in the global script scope.
+ {
+ type Function = () => void;
+ let value: Function;
+ }
`,
],
invalid: [
diff --git a/packages/website/src/components/lib/parseConfig.ts b/packages/website/src/components/lib/parseConfig.ts
index 2915e9f350db..29cd8d6f4a91 100644
--- a/packages/website/src/components/lib/parseConfig.ts
+++ b/packages/website/src/components/lib/parseConfig.ts
@@ -55,7 +55,7 @@ export function parseTSConfig(code?: string): TSConfig {
const moduleRegexp = /(module\.exports\s*=)/g;
function constrainedScopeEval(obj: string): unknown {
- // eslint-disable-next-line @typescript-eslint/no-implied-eval
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval, @typescript-eslint/no-unsafe-call
return new Function(`
"use strict";
var module = { exports: {} };