diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index fa049b2cfb27..18f5b8797272 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -184,6 +184,7 @@ In these cases, we create what we call an extension rule; a rule within our plug
| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | |
| [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | |
| [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | | :wrench: | |
+| [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | disallow `this` keywords outside of classes or class-like objects | | | |
| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | |
| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
diff --git a/packages/eslint-plugin/docs/rules/no-invalid-this.md b/packages/eslint-plugin/docs/rules/no-invalid-this.md
new file mode 100644
index 000000000000..34e5fb8e8754
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-invalid-this.md
@@ -0,0 +1,26 @@
+# disallow `this` keywords outside of classes or class-like objects (`no-invalid-this`)
+
+## Rule Details
+
+This rule extends the base [`eslint/no-invalid-this`](https://eslint.org/docs/rules/no-invalid-this) rule.
+It supports all options and features of the base rule.
+
+## How to use
+
+```cjson
+{
+ // note you must disable the base rule as it can report incorrect errors
+ "no-invalid-this": "off",
+ "@typescript-eslint/no-invalid-this": ["error"]
+}
+```
+
+## Options
+
+See [`eslint/no-invalid-this` options](https://eslint.org/docs/rules/no-invalid-this#options).
+
+## When Not To Use It
+
+When you are indifferent as to how your variables are initialized.
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-invalid-this.md)
diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json
index c5575d4970d6..990ddbd5aec2 100644
--- a/packages/eslint-plugin/src/configs/all.json
+++ b/packages/eslint-plugin/src/configs/all.json
@@ -45,6 +45,8 @@
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-implied-eval": "error",
"@typescript-eslint/no-inferrable-types": "error",
+ "no-invalid-this": "off",
+ "@typescript-eslint/no-invalid-this": "error",
"no-magic-numbers": "off",
"@typescript-eslint/no-magic-numbers": "error",
"@typescript-eslint/no-misused-new": "error",
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index 29db58b681ab..d3c450bae235 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -38,6 +38,7 @@ import noFloatingPromises from './no-floating-promises';
import noForInArray from './no-for-in-array';
import noImpliedEval from './no-implied-eval';
import noInferrableTypes from './no-inferrable-types';
+import noInvalidThis from './no-invalid-this';
import noMagicNumbers from './no-magic-numbers';
import noMisusedNew from './no-misused-new';
import noMisusedPromises from './no-misused-promises';
@@ -133,6 +134,7 @@ export default {
'no-for-in-array': noForInArray,
'no-implied-eval': noImpliedEval,
'no-inferrable-types': noInferrableTypes,
+ 'no-invalid-this': noInvalidThis,
'no-magic-numbers': noMagicNumbers,
'no-misused-new': noMisusedNew,
'no-misused-promises': noMisusedPromises,
diff --git a/packages/eslint-plugin/src/rules/no-invalid-this.ts b/packages/eslint-plugin/src/rules/no-invalid-this.ts
new file mode 100644
index 000000000000..d192931b72c5
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-invalid-this.ts
@@ -0,0 +1,86 @@
+import { TSESTree } from '@typescript-eslint/experimental-utils';
+import baseRule from 'eslint/lib/rules/no-invalid-this';
+import {
+ InferOptionsTypeFromRule,
+ InferMessageIdsTypeFromRule,
+ createRule,
+ deepMerge,
+} from '../util';
+
+export type Options = InferOptionsTypeFromRule;
+export type MessageIds = InferMessageIdsTypeFromRule;
+
+const schema = deepMerge(
+ Array.isArray(baseRule.meta.schema)
+ ? baseRule.meta.schema[0]
+ : baseRule.meta.schema,
+ {
+ properties: {
+ capIsConstructor: {
+ type: 'boolean',
+ default: true,
+ },
+ },
+ },
+);
+
+export default createRule({
+ name: 'no-invalid-this',
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'disallow `this` keywords outside of classes or class-like objects',
+ category: 'Best Practices',
+ recommended: false,
+ extendsBaseRule: true,
+ },
+ messages: baseRule.meta.messages,
+ schema: [schema],
+ },
+ defaultOptions: [{ capIsConstructor: true }],
+ create(context) {
+ const rules = baseRule.create(context);
+ let argList: Array = [];
+
+ return {
+ ...rules,
+ FunctionDeclaration(node: TSESTree.FunctionDeclaration): void {
+ const names = node?.params.map(
+ (param: TSESTree.Parameter) => param?.name,
+ );
+ argList.push(names);
+ // baseRule's work
+ rules.FunctionDeclaration(node);
+ },
+ 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void {
+ argList.pop();
+ // baseRule's work
+ rules['FunctionDeclaration:exit'](node);
+ },
+ FunctionExpression(node: TSESTree.FunctionExpression): void {
+ const names = node?.params.map(
+ (param: TSESTree.Parameter) => param?.name,
+ );
+ argList.push(names);
+ // baseRule's work
+ rules.FunctionExpression(node);
+ },
+ 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void {
+ argList.pop();
+ // baseRule's work
+ rules['FunctionExpression:exit'](node);
+ },
+ ThisExpression(node: TSESTree.ThisExpression) {
+ const lastFnArg = argList[argList.length - 1];
+
+ if (lastFnArg?.some((name: string) => name === 'this')) {
+ return;
+ }
+
+ // baseRule's work
+ rules.ThisExpression(node);
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts
new file mode 100644
index 000000000000..0070fefb1bea
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts
@@ -0,0 +1,465 @@
+import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
+import rule from '../../src/rules/no-invalid-this';
+import { RuleTester, getFixturesRootDir } from '../RuleTester';
+
+const rootPath = getFixturesRootDir();
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ sourceType: 'module',
+ tsconfigRootDir: rootPath,
+ project: './tsconfig.json',
+ },
+});
+
+const error = {
+ messageId: 'unexpectedThis',
+};
+
+ruleTester.run('no-invalid-this', rule, {
+ valid: [
+ `describe('foo', () => {
+ it('does something', function(this: Mocha.Context) {
+ this.timeout(100);
+ // done
+ });
+ });`,
+ `
+ interface SomeType { prop: string }
+ function foo(this: SomeType) {
+ this.prop;
+ }`,
+ `function foo(this: prop){
+ this.propMethod()
+ }`,
+ ' z(function(x,this: context ) { console.log(x, this) });',
+ // https://github.com/eslint/eslint/issues/3287
+
+ 'function foo() { /** @this Obj*/ return function bar() { console.log(this); z(x => console.log(x, this)); }; }',
+
+ // https://github.com/eslint/eslint/issues/6824
+
+ 'var Ctor = function() { console.log(this); z(x => console.log(x, this)); }',
+ // Constructors.
+ {
+ code:
+ 'function Foo() { console.log(this); z(x => console.log(x, this)); }',
+ },
+ {
+ code:
+ 'function Foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{}], // test the default value in schema
+ },
+ {
+ code:
+ 'function Foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: true }], // test explicitly set option to the default value
+ },
+ {
+ code:
+ 'var Foo = function Foo() { console.log(this); z(x => console.log(x, this)); };',
+ },
+ {
+ code:
+ 'class A {constructor() { console.log(this); z(x => console.log(x, this)); }};',
+ },
+
+ // On a property.
+ {
+ code:
+ 'var obj = {foo: function() { console.log(this); z(x => console.log(x, this)); }};',
+ },
+ {
+ code:
+ 'var obj = {foo() { console.log(this); z(x => console.log(x, this)); }};',
+ },
+ {
+ code:
+ 'var obj = {foo: foo || function() { console.log(this); z(x => console.log(x, this)); }};',
+ },
+ {
+ code:
+ 'var obj = {foo: hasNative ? foo : function() { console.log(this); z(x => console.log(x, this)); }};',
+ },
+ {
+ code:
+ 'var obj = {foo: (function() { return function() { console.log(this); z(x => console.log(x, this)); }; })()};',
+ },
+ {
+ code:
+ 'Object.defineProperty(obj, "foo", {value: function() { console.log(this); z(x => console.log(x, this)); }})',
+ },
+ {
+ code:
+ 'Object.defineProperties(obj, {foo: {value: function() { console.log(this); z(x => console.log(x, this)); }}})',
+ },
+
+ // Assigns to a property.
+ {
+ code:
+ 'obj.foo = function() { console.log(this); z(x => console.log(x, this)); };',
+ },
+ {
+ code:
+ 'obj.foo = foo || function() { console.log(this); z(x => console.log(x, this)); };',
+ },
+ {
+ code:
+ 'obj.foo = foo ? bar : function() { console.log(this); z(x => console.log(x, this)); };',
+ },
+ {
+ code:
+ 'obj.foo = (function() { return function() { console.log(this); z(x => console.log(x, this)); }; })();',
+ },
+ {
+ code:
+ 'obj.foo = (() => function() { console.log(this); z(x => console.log(x, this)); })();',
+ },
+
+ // Bind/Call/Apply
+ '(function() { console.log(this); z(x => console.log(x, this)); }).call(obj);',
+ 'var foo = function() { console.log(this); z(x => console.log(x, this)); }.bind(obj);',
+ 'Reflect.apply(function() { console.log(this); z(x => console.log(x, this)); }, obj, []);',
+ '(function() { console.log(this); z(x => console.log(x, this)); }).apply(obj);',
+
+ // Class Instance Methods.
+ 'class A {foo() { console.log(this); z(x => console.log(x, this)); }};',
+
+ // Array methods.
+
+ 'Array.from([], function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.every(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.filter(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.find(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.findIndex(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.forEach(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.map(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ 'foo.some(function() { console.log(this); z(x => console.log(x, this)); }, obj);',
+
+ // @this tag.
+
+ '/** @this Obj */ function foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ 'foo(/* @this Obj */ function() { console.log(this); z(x => console.log(x, this)); });',
+
+ '/**\n * @returns {void}\n * @this Obj\n */\nfunction foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ 'Ctor = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ 'function foo(Ctor = function() { console.log(this); z(x => console.log(x, this)); }) {}',
+
+ '[obj.method = function() { console.log(this); z(x => console.log(x, this)); }] = a',
+
+ // Static
+
+ 'class A {static foo() { console.log(this); z(x => console.log(x, this)); }};',
+ ],
+
+ invalid: [
+ {
+ code: `
+ interface SomeType { prop: string }
+ function foo() {
+ this.prop;
+ }`,
+ errors: [error],
+ },
+ // Global.
+ {
+ code: 'console.log(this); z(x => console.log(x, this));',
+
+ errors: [error, error],
+ },
+ {
+ code: 'console.log(this); z(x => console.log(x, this));',
+ parserOptions: {
+ ecmaFeatures: { globalReturn: true },
+ },
+ errors: [error, error],
+ },
+
+ // IIFE.
+ {
+ code:
+ '(function() { console.log(this); z(x => console.log(x, this)); })();',
+
+ errors: [error, error],
+ },
+
+ // Just functions.
+ {
+ code:
+ 'function foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'function foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }], // test that the option doesn't reverse the logic and mistakenly allows lowercase functions
+ errors: [error, error],
+ },
+ {
+ code:
+ 'function Foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }],
+ errors: [error, error],
+ },
+ {
+ code:
+ 'function foo() { "use strict"; console.log(this); z(x => console.log(x, this)); }',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'function Foo() { "use strict"; console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }],
+ errors: [error, error],
+ },
+ {
+ code:
+ 'return function() { console.log(this); z(x => console.log(x, this)); };',
+ parserOptions: {
+ ecmaFeatures: { globalReturn: true },
+ },
+ errors: [error, error],
+ },
+ {
+ code:
+ 'var foo = (function() { console.log(this); z(x => console.log(x, this)); }).bar(obj);',
+
+ errors: [error, error],
+ },
+
+ // Functions in methods.
+ {
+ code:
+ 'var obj = {foo: function() { function foo() { console.log(this); z(x => console.log(x, this)); } foo(); }};',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'var obj = {foo() { function foo() { console.log(this); z(x => console.log(x, this)); } foo(); }};',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'var obj = {foo: function() { return function() { console.log(this); z(x => console.log(x, this)); }; }};',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'var obj = {foo: function() { "use strict"; return function() { console.log(this); z(x => console.log(x, this)); }; }};',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'obj.foo = function() { return function() { console.log(this); z(x => console.log(x, this)); }; };',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'obj.foo = function() { "use strict"; return function() { console.log(this); z(x => console.log(x, this)); }; };',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'class A { foo() { return function() { console.log(this); z(x => console.log(x, this)); }; } }',
+
+ errors: [error, error],
+ },
+
+ // Class Static methods.
+
+ {
+ code:
+ 'obj.foo = (function() { return () => { console.log(this); z(x => console.log(x, this)); }; })();',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'obj.foo = (() => () => { console.log(this); z(x => console.log(x, this)); })();',
+
+ errors: [error, error],
+ },
+ // Bind/Call/Apply
+
+ {
+ code:
+ 'var foo = function() { console.log(this); z(x => console.log(x, this)); }.bind(null);',
+
+ errors: [error, error],
+ },
+
+ {
+ code:
+ '(function() { console.log(this); z(x => console.log(x, this)); }).call(undefined);',
+
+ errors: [error, error],
+ },
+
+ {
+ code:
+ '(function() { console.log(this); z(x => console.log(x, this)); }).apply(void 0);',
+
+ errors: [error, error],
+ },
+
+ // Array methods.
+ {
+ code:
+ 'Array.from([], function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.every(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.filter(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.find(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.findIndex(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.forEach(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.map(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'foo.some(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+
+ {
+ code:
+ 'foo.forEach(function() { console.log(this); z(x => console.log(x, this)); }, null);',
+
+ errors: [error, error],
+ },
+
+ // @this tag.
+
+ {
+ code:
+ '/** @returns {void} */ function foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ '/** @this Obj */ foo(function() { console.log(this); z(x => console.log(x, this)); });',
+
+ errors: [error, error],
+ },
+
+ // https://github.com/eslint/eslint/issues/3254
+ {
+ code:
+ 'function foo() { console.log(this); z(x => console.log(x, this)); }',
+
+ errors: [error, error],
+ },
+
+ {
+ code:
+ 'var Ctor = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }],
+ errors: [error, error],
+ },
+ {
+ code:
+ 'var func = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'var func = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }],
+ errors: [error, error],
+ },
+
+ {
+ code:
+ 'Ctor = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }],
+ errors: [error, error],
+ },
+ {
+ code:
+ 'func = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ errors: [error, error],
+ },
+ {
+ code:
+ 'func = function() { console.log(this); z(x => console.log(x, this)); }',
+
+ options: [{ capIsConstructor: false }],
+ errors: [error, error],
+ },
+
+ {
+ code:
+ 'function foo(func = function() { console.log(this); z(x => console.log(x, this)); }) {}',
+
+ errors: [error, error],
+ },
+
+ {
+ code:
+ '[func = function() { console.log(this); z(x => console.log(x, this)); }] = a',
+
+ errors: [error, error],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts
index ea60d9b31697..076ed5d13e02 100644
--- a/packages/eslint-plugin/typings/eslint-rules.d.ts
+++ b/packages/eslint-plugin/typings/eslint-rules.d.ts
@@ -543,3 +543,22 @@ declare module 'eslint/lib/rules/no-extra-semi' {
>;
export = rule;
}
+
+declare module 'eslint/lib/rules/no-invalid-this' {
+ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
+
+ const rule: TSESLint.RuleModule<
+ 'unexpectedThis',
+ [{ capIsConstructor?: boolean }?],
+ {
+ Program(node: TSESTree.Program): void;
+ 'Program:exit'(node: TSESTree.Program): void;
+ FunctionDeclaration(node: TSESTree.FunctionDeclaration): void;
+ 'FunctionDeclaration:exit'(node: TSESTree.FunctionDeclaration): void;
+ FunctionExpression(node: TSESTree.FunctionExpression): void;
+ 'FunctionExpression:exit'(node: TSESTree.FunctionExpression): void;
+ ThisExpression(node: TSESTree.ThisExpression): void;
+ }
+ >;
+ export = rule;
+}