diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index f6e70ef98ac..dd269dc2484 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -192,6 +192,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 00000000000..ac9dc30122c
--- /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 adds support for TypeScript's `this` parameters.
+
+## 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 0e103162f5f..cf27dc57be5 100644
--- a/packages/eslint-plugin/src/configs/all.json
+++ b/packages/eslint-plugin/src/configs/all.json
@@ -52,6 +52,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",
"@typescript-eslint/no-invalid-void-type": "error",
"no-magic-numbers": "off",
"@typescript-eslint/no-magic-numbers": "error",
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index 2d8e6830c4f..9c1c26444d7 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -41,6 +41,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 noInvalidVoidType from './no-invalid-void-type';
import noMagicNumbers from './no-magic-numbers';
import noMisusedNew from './no-misused-new';
@@ -145,6 +146,7 @@ export default {
'no-for-in-array': noForInArray,
'no-implied-eval': noImpliedEval,
'no-inferrable-types': noInferrableTypes,
+ 'no-invalid-this': noInvalidThis,
'no-invalid-void-type': noInvalidVoidType,
'no-magic-numbers': noMagicNumbers,
'no-misused-new': noMisusedNew,
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 00000000000..186d3378846
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-invalid-this.ts
@@ -0,0 +1,78 @@
+import {
+ TSESTree,
+ AST_NODE_TYPES,
+} from '@typescript-eslint/experimental-utils';
+import baseRule from 'eslint/lib/rules/no-invalid-this';
+import {
+ InferOptionsTypeFromRule,
+ createRule,
+ InferMessageIdsTypeFromRule,
+} from '../util';
+
+export type Options = InferOptionsTypeFromRule;
+export type MessageIds = InferMessageIdsTypeFromRule;
+
+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: baseRule.meta.schema,
+ },
+ defaultOptions: [{ capIsConstructor: true }],
+ create(context) {
+ const rules = baseRule.create(context);
+ const argList: boolean[] = [];
+
+ return {
+ ...rules,
+ FunctionDeclaration(node: TSESTree.FunctionDeclaration): void {
+ argList.push(
+ node.params.some(
+ param =>
+ param.type === AST_NODE_TYPES.Identifier && param.name === 'this',
+ ),
+ );
+ // 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 {
+ argList.push(
+ node.params.some(
+ param =>
+ param.type === AST_NODE_TYPES.Identifier && param.name === 'this',
+ ),
+ );
+ // 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): void {
+ const lastFnArg = argList[argList.length - 1];
+
+ if (lastFnArg) {
+ 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 00000000000..93701a2dc32
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-invalid-this.test.ts
@@ -0,0 +1,899 @@
+import rule from '../../src/rules/no-invalid-this';
+import { RuleTester } from '../RuleTester';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ sourceType: 'module',
+ },
+});
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const errors: any = [
+ { message: "Unexpected 'this'." },
+ { message: "Unexpected 'this'." },
+];
+
+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));
+ },
+);
+ `,
+
+ `
+/**
+ * @returns {void}
+ * @this Obj
+ */
+function 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: [{ message: "Unexpected 'this'." }],
+ },
+ // Global.
+ {
+ code: `
+console.log(this);
+z(x => console.log(x, this));
+ `,
+
+ errors,
+ },
+ {
+ code: `
+console.log(this);
+z(x => console.log(x, this));
+ `,
+ parserOptions: {
+ ecmaFeatures: { globalReturn: true },
+ },
+ errors,
+ },
+
+ // IIFE.
+ {
+ code: `
+(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+})();
+ `,
+
+ errors,
+ },
+
+ // Just functions.
+ {
+ code: `
+function foo() {
+ console.log(this);
+ z(x => console.log(x, this));
+}
+ `,
+
+ errors,
+ },
+ {
+ 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,
+ },
+ {
+ code: `
+function Foo() {
+ console.log(this);
+ z(x => console.log(x, this));
+}
+ `,
+
+ options: [{ capIsConstructor: false }],
+ errors,
+ },
+ {
+ code: `
+function foo() {
+ 'use strict';
+ console.log(this);
+ z(x => console.log(x, this));
+}
+ `,
+
+ errors,
+ },
+ {
+ code: `
+function Foo() {
+ 'use strict';
+ console.log(this);
+ z(x => console.log(x, this));
+}
+ `,
+
+ options: [{ capIsConstructor: false }],
+ errors,
+ },
+ {
+ code: `
+return function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+ parserOptions: {
+ ecmaFeatures: { globalReturn: true },
+ },
+ errors,
+ },
+ {
+ code: `
+var foo = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+}.bar(obj);
+ `,
+
+ errors,
+ },
+
+ // Functions in methods.
+ {
+ code: `
+var obj = {
+ foo: function() {
+ function foo() {
+ console.log(this);
+ z(x => console.log(x, this));
+ }
+ foo();
+ },
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+var obj = {
+ foo() {
+ function foo() {
+ console.log(this);
+ z(x => console.log(x, this));
+ }
+ foo();
+ },
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+var obj = {
+ foo: function() {
+ return function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ };
+ },
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+var obj = {
+ foo: function() {
+ 'use strict';
+ return function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ };
+ },
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+obj.foo = function() {
+ return function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ };
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+obj.foo = function() {
+ 'use strict';
+ return function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ };
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+class A {
+ foo() {
+ return function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ };
+ }
+}
+ `,
+
+ errors,
+ },
+
+ // Class Static methods.
+
+ {
+ code: `
+obj.foo = (function() {
+ return () => {
+ console.log(this);
+ z(x => console.log(x, this));
+ };
+})();
+ `,
+
+ errors,
+ },
+ {
+ code: `
+obj.foo = (() => () => {
+ console.log(this);
+ z(x => console.log(x, this));
+})();
+ `,
+
+ errors,
+ },
+ // Bind/Call/Apply
+
+ {
+ code: `
+var foo = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+}.bind(null);
+ `,
+
+ errors,
+ },
+
+ {
+ code: `
+(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+}.call(undefined));
+ `,
+
+ errors,
+ },
+
+ {
+ code: `
+(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+}.apply(void 0));
+ `,
+
+ errors,
+ },
+
+ // Array methods.
+ {
+ code: `
+Array.from([], function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.every(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.filter(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.find(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.findIndex(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.forEach(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.map(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+ {
+ code: `
+foo.some(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+
+ {
+ code: `
+foo.forEach(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+}, null);
+ `,
+
+ errors,
+ },
+
+ // @this tag.
+
+ {
+ code: `
+/** @returns {void} */ function foo() {
+ console.log(this);
+ z(x => console.log(x, this));
+}
+ `,
+
+ errors,
+ },
+ {
+ code: `
+/** @this Obj */ foo(function() {
+ console.log(this);
+ z(x => console.log(x, this));
+});
+ `,
+
+ errors,
+ },
+
+ {
+ code: `
+var Ctor = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+
+ options: [{ capIsConstructor: false }],
+ errors,
+ },
+ {
+ code: `
+var func = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+var func = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+
+ options: [{ capIsConstructor: false }],
+ errors,
+ },
+
+ {
+ code: `
+Ctor = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+
+ options: [{ capIsConstructor: false }],
+ errors,
+ },
+ {
+ code: `
+func = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+
+ errors,
+ },
+ {
+ code: `
+func = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+};
+ `,
+
+ options: [{ capIsConstructor: false }],
+ errors,
+ },
+
+ {
+ code: `
+function foo(
+ func = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ },
+) {}
+ `,
+
+ errors,
+ },
+
+ {
+ code: `
+[
+ func = function() {
+ console.log(this);
+ z(x => console.log(x, this));
+ },
+] = a;
+ `,
+
+ errors,
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts
index 0bac88823e2..b4ec293e4f5 100644
--- a/packages/eslint-plugin/typings/eslint-rules.d.ts
+++ b/packages/eslint-plugin/typings/eslint-rules.d.ts
@@ -641,6 +641,28 @@ declare module 'eslint/lib/rules/init-declarations' {
export = rule;
}
+declare module 'eslint/lib/rules/no-invalid-this' {
+ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
+
+ const rule: TSESLint.RuleModule<
+ never,
+ [
+ {
+ 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;
+}
declare module 'eslint/lib/rules/dot-notation' {
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';