Skip to content

Commit

Permalink
New: lines-between-class-members rule (fixes #5949) (#9141)
Browse files Browse the repository at this point in the history
  • Loading branch information
aladdin-add authored and not-an-aardvark committed Oct 2, 2017
1 parent 9d3f5ad commit ee99876
Show file tree
Hide file tree
Showing 5 changed files with 276 additions and 0 deletions.
1 change: 1 addition & 0 deletions conf/eslint-recommended.js
Expand Up @@ -63,6 +63,7 @@ module.exports = {
"linebreak-style": "off",
"lines-around-comment": "off",
"lines-around-directive": "off",
"lines-between-class-members": "off",
"max-depth": "off",
"max-len": "off",
"max-lines": "off",
Expand Down
107 changes: 107 additions & 0 deletions docs/rules/lines-between-class-members.md
@@ -0,0 +1,107 @@
# 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 lines-between-class-members: ["error", "always"]*/
class MyClass {
foo() {
//...
}
bar() {
//...
}
}
```

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

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

bar() {
//...
}
}
```

### Options

This rule has a string option and an object option.

String option:

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

Object option:

* `"exceptAfterSingleLine": "false"`(default) **do not** skip checking empty lines after singleline class members
* `"exceptAfterSingleLine": "true"` skip checking empty lines after singleline class members

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

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

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

baz(){}
}
```

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

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

baz(){}
}

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

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

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

qux(){}
}
```

## When Not To Use It

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

## Related Rules

* [padded-blocks](padded-blocks.md)
* [padding-line-between-statement](padding-line-between-statement.md)
* [requirePaddingNewLinesAfterBlocks](http://jscs.info/rule/requirePaddingNewLinesAfterBlocks)
* [disallowPaddingNewLinesAfterBlocks](http://jscs.info/rule/disallowPaddingNewLinesAfterBlocks)
5 changes: 5 additions & 0 deletions docs/rules/padded-blocks.md
Expand Up @@ -354,3 +354,8 @@ if (a) {
## When Not To Use It

You can turn this rule off if you are not concerned with the consistency of padding within blocks.

## Related Rules

* [lines-between-class-members](lines-between-class-members.md)
* [padding-line-between-statement](padding-line-between-statement.md)
91 changes: 91 additions & 0 deletions lib/rules/lines-between-class-members.js
@@ -0,0 +1,91 @@
/**
* @fileoverview Rule to check empty newline between class members
* @author 薛定谔的猫<hh_2013@foxmail.com>
*/
"use strict";

const astUtils = require("../ast-utils");

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
meta: {
docs: {
description: "require or disallow an empty line between class members",
category: "Stylistic Issues",
recommended: false
},

fixable: "whitespace",

schema: [
{
enum: ["always", "never"]
},
{
type: "object",
properties: {
exceptAfterSingleLine: {
type: "boolean"
}
},
additionalProperties: false
}
]
},

create(context) {

const options = [];

options[0] = context.options[0] || "always";
options[1] = context.options[1] || { exceptAfterSingleLine: false };

const ALWAYS_MESSAGE = "Expected blank line between class members.";
const NEVER_MESSAGE = "Unexpected blank line between class members.";

const sourceCode = context.getSourceCode();

/**
* Checks if there is padding between two tokens
* @param {Token} first The first token
* @param {Token} second The second token
* @returns {boolean} True if there is at least a line between the tokens
*/
function isPaddingBetweenTokens(first, second) {
return second.loc.start.line - first.loc.end.line >= 2;
}

return {
ClassBody(node) {
const body = node.body;

for (let i = 0; i < body.length - 1; i++) {
const curFirst = sourceCode.getFirstToken(body[i]);
const curLast = sourceCode.getLastToken(body[i]);
const comments = sourceCode.getCommentsBefore(body[i + 1]);
const nextFirst = comments.length ? comments[0] : sourceCode.getFirstToken(body[i + 1]);
const isPadded = isPaddingBetweenTokens(curLast, nextFirst);
const isMulti = !astUtils.isTokenOnSameLine(curFirst, curLast);
const skip = !isMulti && options[1].exceptAfterSingleLine;


if ((options[0] === "always" && !skip && !isPadded) ||
(options[0] === "never" && isPadded)) {
context.report({
node: body[i + 1],
message: isPadded ? NEVER_MESSAGE : ALWAYS_MESSAGE,
fix(fixer) {
return isPadded
? fixer.replaceTextRange([curLast.range[1], nextFirst.range[0]], "\n")
: fixer.insertTextAfter(curLast, "\n");
}
});
}
}
}
};
}
};
72 changes: 72 additions & 0 deletions tests/lib/rules/lines-between-class-members.js
@@ -0,0 +1,72 @@
/**
* @fileoverview Tests for lines-between-class-members rule.
* @author 薛定谔的猫<hh_2013@foxmail.com>
*/

"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

const rule = require("../../../lib/rules/lines-between-class-members");
const RuleTester = require("../../../lib/testers/rule-tester");

//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------

const ALWAYS_MESSAGE = "Expected blank line between class members.";
const NEVER_MESSAGE = "Unexpected blank line between class members.";

//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });

ruleTester.run("lines-between-class-members", rule, {
valid: [
"class foo{}",
"class foo{;;}",
"class foo{\n\n}",
"class foo{constructor(){}\n}",
"class foo{\nconstructor(){}}",

"class foo{ bar(){}\n\nbaz(){}}",
"class foo{ bar(){}\n\n/*comments*/baz(){}}",
"class foo{ bar(){}\n\n//comments\nbaz(){}}",

"class foo{ bar(){}\n\n;;baz(){}}",
"class foo{ bar(){};\n\nbaz(){}}",

{ code: "class foo{ bar(){}\nbaz(){}}", options: ["never"] },
{ code: "class foo{ bar(){}\n/*comments*/baz(){}}", options: ["never"] },
{ code: "class foo{ bar(){}\n//comments\nbaz(){}}", options: ["never"] },

{ code: "class foo{ bar(){}\n\nbaz(){}}", options: ["always"] },
{ code: "class foo{ bar(){}\n\n/*comments*/baz(){}}", options: ["always"] },
{ code: "class foo{ bar(){}\n\n//comments\nbaz(){}}", options: ["always"] },

{ code: "class foo{ bar(){}\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] },
{ code: "class foo{ bar(){\n}\n\nbaz(){}}", options: ["always", { exceptAfterSingleLine: true }] }
],
invalid: [
{
code: "class foo{ bar(){}\nbaz(){}}",
output: "class foo{ bar(){}\n\nbaz(){}}",
options: ["always"],
errors: [{ message: ALWAYS_MESSAGE }]
}, {
code: "class foo{ bar(){}\n\nbaz(){}}",
output: "class foo{ bar(){}\nbaz(){}}",
options: ["never"],
errors: [{ message: NEVER_MESSAGE }]
}, {
code: "class foo{ bar(){\n}\nbaz(){}}",
output: "class foo{ bar(){\n}\n\nbaz(){}}",
options: ["always", { exceptAfterSingleLine: true }],
errors: [{ message: ALWAYS_MESSAGE }]
}
]
});

0 comments on commit ee99876

Please sign in to comment.