Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat(eslint-plugin): [no-unsafe-call] check calls of Function#10010

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletionspackages/eslint-plugin/docs/rules/no-unsafe-call.mdx
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,6 +59,34 @@ String.raw`foo`;
</TabItem>
</Tabs>

## 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`.

<Tabs>
<TabItem value="❌ Incorrect">

```ts
const f: Function = () => {};
f();
```

</TabItem>
</Tabs>

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
}
```

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

[Praise] Great example, really clear and concise.

kirkwaiblinger reacted with heart emoji

## 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.
Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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)
52 changes: 48 additions & 4 deletionspackages/eslint-plugin/src/rules/no-unsafe-call.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,6 +6,7 @@
getConstrainedTypeAtLocation,
getParserServices,
getThisExpression,
isBuiltinSymbolLike,
isTypeAnyType,
} from '../util';

Expand All@@ -25,13 +26,13 @@
requiresTypeChecking: true,
},
messages: {
unsafeCall: 'Unsafe call ofan {{type}} typed value.',
unsafeCall: 'Unsafe call ofa(n) {{type}} typed value.',
unsafeCallThis: [
'Unsafe call ofan `any` typed value. `this` is typed as`any`.',
'Unsafe call ofa(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 ofan anytype value.',
unsafeTemplateTag: 'Unsafeany typed template tag.',
unsafeNew: 'Unsafe construction ofa(n) {{type}} typed value.',
unsafeTemplateTag: 'Unsafeuse of a(n) {{type}} typed template tag.',
},
schema: [],
},
Expand DownExpand Up@@ -74,6 +75,49 @@
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;

Check warning on line 107 in packages/eslint-plugin/src/rules/no-unsafe-call.ts

View check run for this annotation

Codecov/ codecov/patch

packages/eslint-plugin/src/rules/no-unsafe-call.ts#L107

Added line #L107 was not covered by tests
}
} else if (callSignatures.length > 0) {
return;
}

context.report({
node: reportingNode,
messageId,
data: {
type: '`Function`',
},
});
return;
}
}

Expand Down
View file
Open in desktop

Some generated files are not rendered by default. Learn more abouthow customized files appear on GitHub.

177 changes: 177 additions & 0 deletionspackages/eslint-plugin/tests/rules/no-unsafe-call.test.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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: [
{
Expand DownExpand Up@@ -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`',
},
},
],
},
],
});
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,8 +9,12 @@ ruleTester.run('no-unsafe-function-type', rule, {
'let value: () => void;',
'let value: <T>(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: [
Expand Down
2 changes: 1 addition & 1 deletionpackages/website/src/components/lib/parseConfig.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
Copy link
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

To be clear,

// this is the no-implied-eval violationconstyoloFunction=Function('inline function');// this is the no-unsafe-call violationyoloFunction();

They just happen to be on the same line here. So, this isn't a case of these rules stepping on each others' toes IMO.

JoshuaKGoldberg reacted with thumbs up emoji
return new Function(`
"use strict";
var module = { exports: {} };
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp