Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(eslint-plugin): add extension rule lines-between-class-members #1684

Merged
merged 6 commits into from May 12, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/eslint-plugin/README.md
Expand Up @@ -178,6 +178,7 @@ In these cases, we create what we call an extension rule; a rule within our plug
| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | |
| [`@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/lines-between-class-members`](./docs/rules/lines-between-class-members.md) | Require or disallow an empty line between class members | | :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: | | |
Expand Down
171 changes: 171 additions & 0 deletions packages/eslint-plugin/docs/rules/lines-between-class-members.md
@@ -0,0 +1,171 @@
# Require or disallow an empty line between class members (`lines-between-class-members`)

This rule improves readability by enforcing lines between class members. It will not check empty lines before the first member and after the last member, since that is already taken care of by padded-blocks.

## Rule Details

Examples of **incorrect** code for this rule:

```js
/* eslint @typescript-eslint/lines-between-class-members: ["error", "always"]*/
class MyClass {
foo() {
//...
}
bar() {
//...
}
baz(a: string): void;
baz(a: string, b: number): void;
baz(a: string, b?: number) {

}
}
```

Examples of **correct** code for this rule:

```js
/* eslint @typescript-eslint/lines-between-class-members: ["error", "always"]*/
class MyClass {
foo() {
//...
}

bar() {
//...
}

baz(a: string): void;
baz(a: string, b: number): void;
baz(a: string, b?: number) {

}
}
```

**_This rule was taken from the ESLint core rule `lines-between-class-members`._**
**_Available options and test cases may vary depending on the version of ESLint installed in the system._**

### Options

This rule has a string option and an object option.

String option:

- `"always"`(default) require an empty line after class members
- `"never"` disallows an empty line after class members

Object option:

- `"exceptAfterSingleLine": false`(default) **do not** skip checking empty lines after single-line class members
- `"exceptAfterSingleLine": true` skip checking empty lines after single-line class members
- `"expectAfterOverload": true`(default) skip checking empty lines after overloading class members
- `"expectAfterOverload": false` **do not** skip checking empty lines after overloading class members

Examples of **incorrect** code for this rule with the string option:

```js
/* eslint @typescript-eslint/lines-between-class-members: ["error", "always"]*/
class Foo {
bar() {}
baz() {}
qux(a: string): void;
qux(a: string, b: number): void;
qux(a: string, b?: number) {

}
}

/* eslint @typescript-eslint/lines-between-class-members: ["error", "never"]*/
class Foo {
bar() {}

baz() {}

qux(a: string): void;

qux(a: string, b: number): void;

qux(a: string, b?: number) {

}
}
```

Examples of **correct** code for this rule with the string option:

```js
/* eslint @typescript-eslint/lines-between-class-members: ["error", "always"]*/
class Foo{
bar(){}

baz(){}

qux(a: string): void;
qux(a: string, b: number): void;
qux(a: string, b?: number) {

}
}

/* eslint @typescript-eslint/lines-between-class-members: ["error", "never"]*/
class Foo{
bar(){}
baz(){}
qux(a: string): void;
qux(a: string, b: string): void;
qux(a: string, b?: string) {

}
}
```

## `exceptAfterSingleLine: "true"`

Examples of **correct** code for this rule with the object option `{ "expectAfterSingleLine": true }`:

```js
/* eslint @typescript-eslint/lines-between-class-members: ["error", "always", { exceptAfterSingleLine: true }]*/
class Foo {
bar() {} // single line class member
baz() {
// multi line class member
}

qux(a: string): void;
qux(a: string, b: string): void;
qux(a: string, b?: string) {

}
}
```

## `exceptAfterOverload: "false"`

Examples of **correct** code for this rule with the object option `{ "exceptAfterOverload": false }`:

```js
/* eslint @typescript-eslint/lines-between-class-members: ["error", "always", { exceptAfterOverload: false }]*/
class Foo {
bar() {} // single line class member

baz() {
// multi line class member
}

qux(a: string): void;

qux(a: string, b: string): void;

qux(a: string, b?: string) {

}
}
```

## When Not To Use It

If you don't want to enforce empty lines between class members, you can disable this rule.

<sup>Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/lines-between-class-members.md)</sup>
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/configs/all.json
Expand Up @@ -21,6 +21,8 @@
"@typescript-eslint/func-call-spacing": "error",
"indent": "off",
"@typescript-eslint/indent": "error",
"lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": "error",
"@typescript-eslint/member-delimiter-style": "error",
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/naming-convention": "error",
Expand Down
2 changes: 2 additions & 0 deletions packages/eslint-plugin/src/rules/index.ts
Expand Up @@ -90,6 +90,7 @@ import typeAnnotationSpacing from './type-annotation-spacing';
import typedef from './typedef';
import unboundMethod from './unbound-method';
import unifiedSignatures from './unified-signatures';
import linesBetweenClassMembers from './lines-between-class-members';

export default {
'adjacent-overload-signatures': adjacentOverloadSignatures,
Expand Down Expand Up @@ -184,4 +185,5 @@ export default {
typedef: typedef,
'unbound-method': unboundMethod,
'unified-signatures': unifiedSignatures,
'lines-between-class-members': linesBetweenClassMembers,
};
72 changes: 72 additions & 0 deletions packages/eslint-plugin/src/rules/lines-between-class-members.ts
@@ -0,0 +1,72 @@
import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import baseRule from 'eslint/lib/rules/lines-between-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: 'lines-between-class-members',
meta: {
type: 'layout',
docs: {
description: 'Require or disallow an empty line between class members',
category: 'Stylistic Issues',
recommended: false,
extendsBaseRule: true,
},
fixable: 'whitespace',
schema: [
{
enum: ['always', 'never'],
},
{
type: 'object',
properties: {
exceptAfterSingleLine: {
type: 'boolean',
default: false,
},
exceptAfterOverload: {
type: 'boolean',
default: false,
},
},
additionalProperties: true,
},
],
messages: baseRule.meta.messages,
},
defaultOptions: [
'always',
{
exceptAfterOverload: true,
exceptAfterSingleLine: false,
},
],
create(context, options) {
const rules = baseRule.create(context);
const exceptAfterOverload =
options[1]?.exceptAfterOverload && options[0] === 'always';

function isOverload(node: TSESTree.Node): boolean {
return (
node.type === AST_NODE_TYPES.MethodDefinition &&
node.value.type === AST_NODE_TYPES.TSEmptyBodyFunctionExpression
);
}

return {
ClassBody(node): void {
const body = exceptAfterOverload
? node.body.filter(node => !isOverload(node))
: node.body;

rules.ClassBody({ ...node, body });
},
};
},
});