diff --git a/packages/eslint-plugin/docs/rules/unbound-method.md b/packages/eslint-plugin/docs/rules/unbound-method.md index 0de7b17abcf5..d5bcdef73003 100644 --- a/packages/eslint-plugin/docs/rules/unbound-method.md +++ b/packages/eslint-plugin/docs/rules/unbound-method.md @@ -52,6 +52,7 @@ const innerLog = () => instance.logBound(); The rule accepts an options object with the following property: - `ignoreStatic` to not check whether `static` methods are correctly bound +- `allowSuper` allows `Super` class methods to bound with base class. ### `ignoreStatic` @@ -70,6 +71,24 @@ const { log } = OtherClass; log(); ``` +### `allowSuper` + +Examples of **correct** code for this rule with `{ allowSuper: true }`: + +```ts +class SuperClass { + method1(){ ... } +} + +class baseClass extend SuperClass { + constructor(){ + super(); + this.baseVar = super.method1; + } +} + +``` + ### Example ```json @@ -77,7 +96,8 @@ log(); "@typescript-eslint/unbound-method": [ "error", { - "ignoreStatic": true + "ignoreStatic": true, + "allowSuper": false } ] } diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index c27f732ca34b..8e835d45ca87 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -12,6 +12,7 @@ import * as util from '../util'; interface Config { ignoreStatic: boolean; + allowSuper?: boolean; } export type Options = [Config]; @@ -138,6 +139,9 @@ export default util.createRule({ ignoreStatic: { type: 'boolean', }, + allowSuper: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -147,9 +151,10 @@ export default util.createRule({ defaultOptions: [ { ignoreStatic: false, + allowSuper: false, }, ], - create(context, [{ ignoreStatic }]) { + create(context, [{ ignoreStatic, allowSuper }]) { const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); const currentSourceFile = parserServices.program.getSourceFile( @@ -160,7 +165,7 @@ export default util.createRule({ 'MemberExpression, OptionalMemberExpression'( node: TSESTree.MemberExpression | TSESTree.OptionalMemberExpression, ): void { - if (isSafeUse(node)) { + if (isSafeUse(node, allowSuper)) { return; } @@ -217,7 +222,7 @@ function isDangerousMethod(symbol: ts.Symbol, ignoreStatic: boolean): boolean { return false; } -function isSafeUse(node: TSESTree.Node): boolean { +function isSafeUse(node: TSESTree.Node, allowSuper = false): boolean { const parent = node.parent; switch (parent?.type) { @@ -250,7 +255,13 @@ function isSafeUse(node: TSESTree.Node): boolean { return ['instanceof', '==', '!=', '===', '!=='].includes(parent.operator); case AST_NODE_TYPES.AssignmentExpression: - return parent.operator === '=' && node === parent.left; + return ( + parent.operator === '=' && + (node === parent.left || + (allowSuper && + (node as TSESTree.MemberExpression) && + node.object.type === AST_NODE_TYPES.Super)) + ); case AST_NODE_TYPES.TSNonNullExpression: case AST_NODE_TYPES.TSAsExpression: diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index 12d6486e09f5..df1b8673a133 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -48,6 +48,32 @@ ruleTester.run('unbound-method', rule, { "['1', '2', '3'].map(Number.parseInt);", '[5.2, 7.1, 3.6].map(Math.floor);', 'const x = console.log;', + { + code: ` +class BaseClass { + x: number = 42; + logThis() { + console.log('x is '); + } +} + +class OtherClass extends BaseClass { + superLogThis: any; + constructor() { + super(); + this.superLogThis = super.logThis; + } +} + +const oc = new OtherClass(); +oc.superLogThis(); + `, + options: [ + { + allowSuper: true, + }, + ], + }, ...[ 'instance.bound();', 'instance.unbound();', @@ -384,5 +410,38 @@ const unbound = new Foo().unbound; }, ], }, + { + code: ` +class BaseClass { + x: number = 42; + logThis() { + console.log('x is '); + } +} + +class OtherClass extends BaseClass { + superLogThis: any; + constructor() { + super(); + this.superLogThis = super.logThis; + } +} + +const oc = new OtherClass(); +oc.superLogThis(); + `, + options: [ + { + allowSuper: false, + }, + ], + errors: [ + { + line: 13, + column: 25, + messageId: 'unbound', + }, + ], + }, ], });