Skip to content

Commit

Permalink
feat(eslint-plugin): add extension [no-dupe-class-members] (#1492)
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed Feb 2, 2020
1 parent 9e7d161 commit b22424e
Show file tree
Hide file tree
Showing 7 changed files with 158 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -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: | |
Expand Down
18 changes: 18 additions & 0 deletions 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"]
}
```

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-no-dupe-class-members.md)</sup>
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.json
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand Down
40 changes: 40 additions & 0 deletions 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<typeof baseRule>;
type MessageIds = util.InferMessageIdsTypeFromRule<typeof baseRule>;

export default util.createRule<Options, MessageIds>({
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);
},
};
},
});
79 changes: 79 additions & 0 deletions 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' } },
],
},
],
});
16 changes: 16 additions & 0 deletions packages/eslint-plugin/typings/eslint-rules.d.ts
Expand Up @@ -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';

Expand Down

0 comments on commit b22424e

Please sign in to comment.