From b22424e7d4a16042a027557f44e9191e0722b38b Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 2 Feb 2020 09:33:45 -0800 Subject: [PATCH] feat(eslint-plugin): add extension [no-dupe-class-members] (#1492) --- packages/eslint-plugin/README.md | 1 + .../docs/rules/no-dupe-class-members.md | 18 +++++ packages/eslint-plugin/src/configs/all.json | 2 + packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-dupe-class-members.ts | 40 ++++++++++ .../tests/rules/no-dupe-class-members.test.ts | 79 +++++++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 16 ++++ 7 files changed, 158 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-dupe-class-members.md create mode 100644 packages/eslint-plugin/src/rules/no-dupe-class-members.ts create mode 100644 packages/eslint-plugin/tests/rules/no-dupe-class-members.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index fd24f0e97f5..0ba91204ab0 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 00000000000..b096ea0c808 --- /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 987fc94a3d7..69797b4f5b6 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 49ca742af3e..fa32bec6986 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 00000000000..bc48b2a843e --- /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 00000000000..72430c6e39b --- /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 9b730372c48..ea60d9b3169 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';