diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index fd24f0e97f50..0ba91204ab04 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -173,6 +173,7 @@ In these cases, we create what we call an extension rule; a rule within our plug
| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | |
| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | |
| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
+| [`@typescript-eslint/no-dupe-class-members`](./docs/rules/no-dupe-class-members.md) | Disallow duplicate class members | | | |
| [`@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: | |
diff --git a/packages/eslint-plugin/docs/rules/no-dupe-class-members.md b/packages/eslint-plugin/docs/rules/no-dupe-class-members.md
new file mode 100644
index 000000000000..b096ea0c808f
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-dupe-class-members.md
@@ -0,0 +1,18 @@
+# Disallow duplicate class members (`no-dupe-class-members`)
+
+## Rule Details
+
+This rule extends the base [`eslint/no-no-dupe-class-members`](https://eslint.org/docs/rules/no-no-dupe-class-members) rule.
+It adds support for TypeScript's method overload definitions.
+
+## How to use
+
+```cjson
+{
+ // note you must disable the base rule as it can report incorrect errors
+ "no-no-dupe-class-members": "off",
+ "@typescript-eslint/no-no-dupe-class-members": ["error"]
+}
+```
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-no-dupe-class-members.md)
diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json
index 987fc94a3d75..69797b4f5b60 100644
--- a/packages/eslint-plugin/src/configs/all.json
+++ b/packages/eslint-plugin/src/configs/all.json
@@ -26,6 +26,8 @@
"@typescript-eslint/naming-convention": "error",
"no-array-constructor": "off",
"@typescript-eslint/no-array-constructor": "error",
+ "no-dupe-class-members": "off",
+ "@typescript-eslint/no-dupe-class-members": "error",
"@typescript-eslint/no-dynamic-delete": "error",
"no-empty-function": "off",
"@typescript-eslint/no-empty-function": "error",
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index 49ca742af3e6..fa32bec69867 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -23,6 +23,7 @@ import memberNaming from './member-naming';
import memberOrdering from './member-ordering';
import namingConvention from './naming-convention';
import noArrayConstructor from './no-array-constructor';
+import noDupeClassMembers from './no-dupe-class-members';
import noDynamicDelete from './no-dynamic-delete';
import noEmptyFunction from './no-empty-function';
import noEmptyInterface from './no-empty-interface';
@@ -110,6 +111,7 @@ export default {
'member-ordering': memberOrdering,
'naming-convention': namingConvention,
'no-array-constructor': noArrayConstructor,
+ 'no-dupe-class-members': noDupeClassMembers,
'no-dynamic-delete': noDynamicDelete,
'no-empty-function': noEmptyFunction,
'no-empty-interface': noEmptyInterface,
diff --git a/packages/eslint-plugin/src/rules/no-dupe-class-members.ts b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts
new file mode 100644
index 000000000000..bc48b2a843e8
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-dupe-class-members.ts
@@ -0,0 +1,40 @@
+import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
+import baseRule from 'eslint/lib/rules/no-dupe-class-members';
+import * as util from '../util';
+
+type Options = util.InferOptionsTypeFromRule;
+type MessageIds = util.InferMessageIdsTypeFromRule;
+
+export default util.createRule({
+ name: 'no-dupe-class-members',
+ meta: {
+ type: 'problem',
+ docs: {
+ description: 'Disallow duplicate class members',
+ category: 'Possible Errors',
+ recommended: false,
+ extendsBaseRule: true,
+ },
+ schema: baseRule.meta.schema,
+ messages: baseRule.meta.messages,
+ },
+ defaultOptions: [],
+ create(context) {
+ const rules = baseRule.create(context);
+
+ return {
+ ...rules,
+ MethodDefinition(node): void {
+ if (node.computed) {
+ return;
+ }
+
+ if (node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression) {
+ return;
+ }
+
+ return rules.MethodDefinition(node);
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/tests/rules/no-dupe-class-members.test.ts b/packages/eslint-plugin/tests/rules/no-dupe-class-members.test.ts
new file mode 100644
index 000000000000..72430c6e39b7
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-dupe-class-members.test.ts
@@ -0,0 +1,79 @@
+import rule from '../../src/rules/no-dupe-class-members';
+import { RuleTester } from '../RuleTester';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+});
+
+ruleTester.run('no-dupe-class-members', rule, {
+ valid: [
+ 'class A { foo() {} bar() {} }',
+ 'class A { static foo() {} foo() {} }',
+ 'class A { get foo() {} set foo(value) {} }',
+ 'class A { static foo() {} get foo() {} set foo(value) {} }',
+ 'class A { foo() { } } class B { foo() { } }',
+ 'class A { [foo]() {} foo() {} }',
+ "class A { 'foo'() {} 'bar'() {} baz() {} }",
+ "class A { *'foo'() {} *'bar'() {} *baz() {} }",
+ "class A { get 'foo'() {} get 'bar'() {} get baz() {} }",
+ 'class A { 1() {} 2() {} }',
+ `
+ class Foo {
+ foo(a: string): string;
+ foo(a: number): number;
+ foo(a: any): any {}
+ }
+ `,
+ ],
+ invalid: [
+ {
+ code: 'class A { foo() {} foo() {} }',
+ errors: [
+ { line: 1, column: 20, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ {
+ code: '!class A { foo() {} foo() {} };',
+ errors: [
+ { line: 1, column: 21, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ {
+ code: "class A { 'foo'() {} 'foo'() {} }",
+ errors: [
+ { line: 1, column: 22, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ {
+ code: 'class A { 10() {} 1e1() {} }',
+ errors: [
+ { line: 1, column: 19, messageId: 'unexpected', data: { name: '10' } },
+ ],
+ },
+ {
+ code: 'class A { foo() {} foo() {} foo() {} }',
+ errors: [
+ { line: 1, column: 20, messageId: 'unexpected', data: { name: 'foo' } },
+ { line: 1, column: 29, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ {
+ code: 'class A { static foo() {} static foo() {} }',
+ errors: [
+ { line: 1, column: 27, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ {
+ code: 'class A { foo() {} get foo() {} }',
+ errors: [
+ { line: 1, column: 20, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ {
+ code: 'class A { set foo(value) {} foo() {} }',
+ errors: [
+ { line: 1, column: 29, messageId: 'unexpected', data: { name: 'foo' } },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts
index f64ebe424a97..3958a520a1c7 100644
--- a/packages/eslint-plugin/typings/eslint-rules.d.ts
+++ b/packages/eslint-plugin/typings/eslint-rules.d.ts
@@ -142,6 +142,22 @@ declare module 'eslint/lib/rules/indent' {
export = rule;
}
+declare module 'eslint/lib/rules/no-dupe-class-members' {
+ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
+
+ const rule: TSESLint.RuleModule<
+ 'unexpected',
+ [],
+ {
+ Program(): void;
+ ClassBody(): void;
+ 'ClassBody:exit'(): void;
+ MethodDefinition(node: TSESTree.MethodDefinition): void;
+ }
+ >;
+ export = rule;
+}
+
declare module 'eslint/lib/rules/no-dupe-args' {
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';