From ee99876ccc76585b0af2c8673d7eb25bb0b972fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=96=9B=E5=AE=9A=E8=B0=94=E7=9A=84=E7=8C=AB?= Date: Mon, 2 Oct 2017 11:49:54 -0500 Subject: [PATCH] New: lines-between-class-members rule (fixes #5949) (#9141) --- conf/eslint-recommended.js | 1 + docs/rules/lines-between-class-members.md | 107 ++++++++++++++++++ docs/rules/padded-blocks.md | 5 + lib/rules/lines-between-class-members.js | 91 +++++++++++++++ .../lib/rules/lines-between-class-members.js | 72 ++++++++++++ 5 files changed, 276 insertions(+) create mode 100644 docs/rules/lines-between-class-members.md create mode 100644 lib/rules/lines-between-class-members.js create mode 100644 tests/lib/rules/lines-between-class-members.js diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js index a6fc9adf561..5d7f4d33c00 100755 --- a/conf/eslint-recommended.js +++ b/conf/eslint-recommended.js @@ -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", diff --git a/docs/rules/lines-between-class-members.md b/docs/rules/lines-between-class-members.md new file mode 100644 index 00000000000..6beeae6eaf9 --- /dev/null +++ b/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) diff --git a/docs/rules/padded-blocks.md b/docs/rules/padded-blocks.md index 1ea3b11990a..dd2c68ccb22 100644 --- a/docs/rules/padded-blocks.md +++ b/docs/rules/padded-blocks.md @@ -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) diff --git a/lib/rules/lines-between-class-members.js b/lib/rules/lines-between-class-members.js new file mode 100644 index 00000000000..85e8c69358c --- /dev/null +++ b/lib/rules/lines-between-class-members.js @@ -0,0 +1,91 @@ +/** + * @fileoverview Rule to check empty newline between class members + * @author 薛定谔的猫 + */ +"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"); + } + }); + } + } + } + }; + } +}; diff --git a/tests/lib/rules/lines-between-class-members.js b/tests/lib/rules/lines-between-class-members.js new file mode 100644 index 00000000000..1304d719a48 --- /dev/null +++ b/tests/lib/rules/lines-between-class-members.js @@ -0,0 +1,72 @@ +/** + * @fileoverview Tests for lines-between-class-members rule. + * @author 薛定谔的猫 + */ + +"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 }] + } + ] +});