diff --git a/conf/eslint.json b/conf/eslint.json index 957101cc846..0a1e9c37a54 100755 --- a/conf/eslint.json +++ b/conf/eslint.json @@ -161,6 +161,7 @@ "key-spacing": "off", "keyword-spacing": "off", "lines-around-comment": "off", + "lines-between-class-methods": "off", "max-depth": "off", "max-len": "off", "max-nested-callbacks": "off", diff --git a/docs/rules/README.md b/docs/rules/README.md index 568453aac19..eb79383e17d 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -170,6 +170,7 @@ These rules relate to style guidelines, and are therefore quite subjective: * [keyword-spacing](keyword-spacing.md): enforce consistent spacing before and after keywords (fixable) * [linebreak-style](linebreak-style.md): enforce consistent linebreak style (fixable) * [lines-around-comment](lines-around-comment.md): require empty lines around comments +* [lines-between-class-methods](lines-between-class-methods.md): enforce consistent padding between class methods * [max-depth](max-depth.md): enforce a maximum depth that blocks can be nested * [max-len](max-len.md): enforce a maximum line length * [max-nested-callbacks](max-nested-callbacks.md): enforce a maximum depth that callbacks can be nested diff --git a/docs/rules/lines-between-class-methods.md b/docs/rules/lines-between-class-methods.md new file mode 100644 index 00000000000..d6fd9ba4be9 --- /dev/null +++ b/docs/rules/lines-between-class-methods.md @@ -0,0 +1,206 @@ +# Enforce lines between class methods (lines-between-class-methods) + +Some style guides require class methods to have a empty line between them. The +goal is to improve readability by visually separating the methods from each +other. + +```js +class T { + a () { + // ... + } + + b () { + // ... + } +} +``` + +Since it's good to have a consistent code style, you should either always add a +empty line between methods or never do it. + +## Rule Details + +This rule enforces empty lines between class methods. + +## Options + +This rule has a string option: + +* `"always"` (default) requires one or more empty line between class methods +* `"never"` disallows empty lines between class methods + +### always + +Examples of **incorrect** code for this rule with the default `"always"` option: + +```js +/*eslint lines-between-class-methods: ["error", "always"]*/ + +class T { + a () { + // ... + } + b () { + // ... + } +} + +class T { + a () { + // ... + } + + b () { + // ... + } + c () { + // ... + } +} + +class T { + a () { + // ... + } + // comment + b () { + // ... + } +} +``` + +Examples of **correct** code for this rule with the default `"always"` option: + +```js +/*eslint lines-between-class-methods: ["error", "always"]*/ + +class T { + a () { + // ... + } + + b () { + // ... + } +} + +class T { + a () { + // ... + } + + b () { + // ... + } + + c () { + // ... + } +} + +class T { + a () { + // ... + } + + + b () { + // ... + } +} + +class T { + a () { + // ... + } + + // comment + b () { + // ... + } +} +``` + +### never + +Examples of **incorrect** code for this rule with the `"never"` option: + +```js +/*eslint lines-between-class-methods: ["error", "never"]*/ + +class T { + a () { + // ... + } + + b () { + // ... + } +} + +class T { + a () { + // ... + } + b () { + // ... + } + + c () { + // ... + } +} + +class T { + a () { + // ... + } + + + b () { + // ... + } +} +``` + +Examples of **correct** code for this rule with the `"never"` option: + +```js +/*eslint lines-between-class-methods: ["error", "never"]*/ + +class T { + a () { + // ... + } + b () { + // ... + } +} + +class T { + a () { + // ... + } + b () { + // ... + } + c () { + // ... + } +} + +class T { + a () { + // ... + } + // comment + b () { + // ... + } +} +``` + +## When Not To Use It + +You can turn this rule off if you are not concerned with the consistency of spacing between class methods. diff --git a/lib/rules/lines-between-class-methods.js b/lib/rules/lines-between-class-methods.js new file mode 100644 index 00000000000..80f9e0c4e18 --- /dev/null +++ b/lib/rules/lines-between-class-methods.js @@ -0,0 +1,68 @@ +/** + * @fileoverview A rule to ensure empty lines between class functions. + * @author Linus Unnebäck + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + var config = context.options[0] || "always"; + + var ALWAYS_MESSAGE = "Class methods must be separated by at least one blank line.", + NEVER_MESSAGE = "Class methods must not be separated by blank lines."; + + var sourceCode = context.getSourceCode(); + + return { + "ClassBody:exit": function(node) { + node.body.reduce(function(prev, next) { + var firstEmptyLine = null; + var nextLine = prev.loc.end.line + 1; + var comments = sourceCode.getComments(prev).trailing; + + for (var i = 0; i < comments.length; i++) { + var comment = comments[i]; + + if (comment.loc.start.line > nextLine) { + firstEmptyLine = nextLine; + break; + } + + nextLine = comment.loc.end.line + 1; + } + + if (firstEmptyLine === null && next.loc.start.line > nextLine) { + firstEmptyLine = nextLine; + } + + if (config === "always" && firstEmptyLine === null) { + context.report({ + node: node, + loc: { line: next.loc.start.line, column: next.loc.start.column }, + message: ALWAYS_MESSAGE + }); + } + + if (config === "never" && firstEmptyLine !== null) { + context.report({ + node: node, + loc: { line: firstEmptyLine, column: 0 }, + message: NEVER_MESSAGE + }); + } + + return next; + }); + } + }; +}; + +module.exports.schema = [ + { + enum: ["always", "never"] + } +]; diff --git a/tests/lib/rules/lines-between-class-methods.js b/tests/lib/rules/lines-between-class-methods.js new file mode 100644 index 00000000000..9ef5d20b146 --- /dev/null +++ b/tests/lib/rules/lines-between-class-methods.js @@ -0,0 +1,243 @@ +/** + * @fileoverview Tests for lines-between-class-methods rule. + * @author Linus Unnebäck + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +var rule = require("../../../lib/rules/lines-between-class-methods"), + RuleTester = require("../../../lib/testers/rule-tester"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +var ruleTester = new RuleTester(), + ALWAYS_MESSAGE = "Class methods must be separated by at least one blank line.", + NEVER_MESSAGE = "Class methods must not be separated by blank lines."; + +ruleTester.run("lines-between-class-methods", rule, { + valid: [ + { + code: "class T {\na() {}\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\nb() {}\nc() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\nb() {}\n\nc() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\nb() {}\nc() {}\nd() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\nb() {}\n\nc() {}\n\nd() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\n// b\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\n/*\n\n\n*/\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n/*\n\n\n*/\n\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n/*\n\n\n*/\n\n/*\n\n\n*/\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\n/**/ b() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n// b\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n/*\n\n\n*/\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n/*\n\n\n*/\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n/*\n\n\n*/\n/*\n\n\n*/\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n/**/ b() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "class T {\na() {}\n\n\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 } + } + ], + invalid: [ + { + code: "class T {\na() {}\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: ALWAYS_MESSAGE, + line: 3, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: NEVER_MESSAGE, + line: 3, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n\nb() {}\nc() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: ALWAYS_MESSAGE, + line: 5, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n\nb() {}\nc() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: NEVER_MESSAGE, + line: 3, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\nb() {}\nc() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: ALWAYS_MESSAGE, + line: 3, + column: 1 + }, + { + message: ALWAYS_MESSAGE, + line: 4, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n\nb() {}\n\nc() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: NEVER_MESSAGE, + line: 3, + column: 1 + }, + { + message: NEVER_MESSAGE, + line: 5, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n\n\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: NEVER_MESSAGE, + line: 3, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n// 1\n// 2\nb() {}\n}", + options: ["always"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: ALWAYS_MESSAGE, + line: 5, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n// 1\n\n// 3\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: NEVER_MESSAGE, + line: 4, + column: 1 + } + ] + }, + { + code: "class T {\na() {}\n\n// 2\n// 3\nb() {}\n}", + options: ["never"], + parserOptions: { ecmaVersion: 6 }, + errors: [ + { + message: NEVER_MESSAGE, + line: 3, + column: 1 + } + ] + } + ] +});