From dccb6ee9f1cd9519c26808d10a5bed8291d0a8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kriszti=C3=A1n=20Balla?= Date: Sun, 18 Oct 2020 21:17:03 +0200 Subject: [PATCH] fix(eslint-plugin): [no-invalid-this] allow "this" in class property definitions (#2685) --- .../src/rules/no-invalid-this.ts | 35 +++++++++--- .../tests/rules/no-invalid-this.test.ts | 55 +++++++++++++++++++ 2 files changed, 83 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-invalid-this.ts b/packages/eslint-plugin/src/rules/no-invalid-this.ts index 8824978a3f3..3f579655f0b 100644 --- a/packages/eslint-plugin/src/rules/no-invalid-this.ts +++ b/packages/eslint-plugin/src/rules/no-invalid-this.ts @@ -31,12 +31,33 @@ export default createRule({ defaultOptions: [{ capIsConstructor: true }], create(context) { const rules = baseRule.create(context); - const argList: boolean[] = []; + + /** + * Since function definitions can be nested we use a stack storing if "this" is valid in the current context. + * + * Example: + * + * function a(this: number) { // valid "this" + * function b() { + * console.log(this); // invalid "this" + * } + * } + * + * When parsing the function declaration of "a" the stack will be: [true] + * When parsing the function declaration of "b" the stack will be: [true, false] + */ + const thisIsValidStack: boolean[] = []; return { ...rules, + ClassProperty(): void { + thisIsValidStack.push(true); + }, + 'ClassProperty:exit'(): void { + thisIsValidStack.pop(); + }, FunctionDeclaration(node: TSESTree.FunctionDeclaration): void { - argList.push( + thisIsValidStack.push( node.params.some( param => param.type === AST_NODE_TYPES.Identifier && param.name === 'this', @@ -46,12 +67,12 @@ export default createRule({ rules.FunctionDeclaration(node); }, 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void { - argList.pop(); + thisIsValidStack.pop(); // baseRule's work rules['FunctionDeclaration:exit'](node); }, FunctionExpression(node: TSESTree.FunctionExpression): void { - argList.push( + thisIsValidStack.push( node.params.some( param => param.type === AST_NODE_TYPES.Identifier && param.name === 'this', @@ -61,14 +82,14 @@ export default createRule({ rules.FunctionExpression(node); }, 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void { - argList.pop(); + thisIsValidStack.pop(); // baseRule's work rules['FunctionExpression:exit'](node); }, ThisExpression(node: TSESTree.ThisExpression): void { - const lastFnArg = argList[argList.length - 1]; + const thisIsValidHere = thisIsValidStack[thisIsValidStack.length - 1]; - if (lastFnArg) { + if (thisIsValidHere) { return; } diff --git a/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts index 0705ba1e9e8..b2c774166a1 100644 --- a/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts +++ b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts @@ -277,6 +277,28 @@ class A { } `, + // Class Properties. + ` +class A { + b = 0; + c = this.b; +} + `, + + ` +class A { + b = new Array(this, 1, 2, 3); +} + `, + + ` +class A { + b = () => { + console.log(this); + }; +} + `, + // Array methods. ` @@ -613,6 +635,9 @@ obj.foo = function () { errors, }, + + // Class Methods. + { code: ` class A { @@ -628,6 +653,36 @@ class A { errors, }, + // Class Properties. + + { + code: ` +class A { + b = new Array(1, 2, function () { + console.log(this); + z(x => console.log(x, this)); + }); +} + `, + + errors, + }, + + { + code: ` +class A { + b = () => { + function c() { + console.log(this); + z(x => console.log(x, this)); + } + }; +} + `, + + errors, + }, + // Class Static methods. {