From 3b98173d25efe4c8fb01ca41dfcd004b11ad6420 Mon Sep 17 00:00:00 2001 From: Ronen Amiel Date: Sat, 4 Oct 2025 23:36:01 +0300 Subject: [PATCH] initial implementation --- .../eslint-plugin/src/rules/unbound-method.ts | 10 ++ .../tests/rules/unbound-method.test.ts | 96 ++++++++++++++++++- 2 files changed, 104 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 49f8dbe23802..0582056c2518 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -328,6 +328,15 @@ function checkIfMethod( } return checkMethod(assignee as ts.FunctionExpression, ignoreStatic); } + case ts.SyntaxKind.PropertySignature: { + const type = (valueDeclaration as ts.PropertySignature).type; + if (type?.kind !== ts.SyntaxKind.FunctionType) { + return { + dangerous: false, + }; + } + return checkMethod(type as ts.FunctionTypeNode, ignoreStatic); + } case ts.SyntaxKind.MethodDeclaration: case ts.SyntaxKind.MethodSignature: { return checkMethod( @@ -343,6 +352,7 @@ function checkIfMethod( function checkMethod( valueDeclaration: | ts.FunctionExpression + | ts.FunctionTypeNode | ts.MethodDeclaration | ts.MethodSignature, ignoreStatic: boolean, diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index aeca1a171d50..b15c55282a86 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -463,7 +463,11 @@ class Foo { type foo = new ({ unbound }: Foo) => void; `, 'const { unbound } = { unbound: () => {} };', - 'function foo({ unbound }: { unbound: () => void } = { unbound: () => {} }) {}', + ` +function foo( + { unbound }: { unbound: (this: void) => void } = { unbound: () => {} }, +) {} + `, // https://github.com/typescript-eslint/typescript-eslint/issues/1866 ` class BaseClass { @@ -480,6 +484,30 @@ class OtherClass extends BaseClass { const oc = new OtherClass(); oc.superLogThis(); `, + ` +interface Foo { + bound: (this: void) => number; +} + +declare const foo: Foo; +foo.bound; + `, + ` +interface Foo { + bound: (this: void) => number; +} + +declare const foo: Foo; +const { bound } = foo; + `, + ` +type Foo = { + bound: (this: void) => number; +}; + +declare const foo: Foo; +const { bound } = foo; + `, ], invalid: [ { @@ -965,7 +993,7 @@ function foo({ unbound }: { unbound: () => string } | Foo) {} errors: [ { line: 5, - messageId: 'unbound', + messageId: 'unboundWithoutThisAnnotation', }, ], }, @@ -1252,5 +1280,69 @@ const f = objectLiteral.f; }, ], }, + { + code: ` +interface Foo { + bound: () => number; +} + +declare const foo: Foo; +foo.bound; + `, + errors: [ + { + line: 7, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, + { + code: ` +interface Foo { + bound: () => number; +} + +declare const foo: Foo; +const { bound } = foo; + `, + errors: [ + { + line: 7, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, + { + code: ` +interface Foo { + bound: (this: Foo) => number; +} + +declare const foo: Foo; +foo.bound; + `, + errors: [ + { + line: 7, + messageId: 'unbound', + }, + ], + }, + { + code: ` +type Foo = { + bound: () => number; +}; + +declare const foo: Foo; +foo.bound; + `, + errors: [ + { + line: 7, + messageId: 'unboundWithoutThisAnnotation', + }, + ], + }, ], });