From 2f8ae1397eef3625fe66636e95b0b312c6ff8a37 Mon Sep 17 00:00:00 2001 From: Vladlen Grachev Date: Tue, 2 Apr 2019 10:04:46 +0300 Subject: [PATCH] Update: support single argument on newline with function-paren-newline (#11406) --- docs/rules/function-paren-newline.md | 51 +++ lib/rules/function-paren-newline.js | 42 ++- tests/lib/rules/function-paren-newline.js | 362 +++++++++++++++++++++- 3 files changed, 450 insertions(+), 5 deletions(-) diff --git a/docs/rules/function-paren-newline.md b/docs/rules/function-paren-newline.md index 0c886bf5356..099021e0ef2 100644 --- a/docs/rules/function-paren-newline.md +++ b/docs/rules/function-paren-newline.md @@ -13,6 +13,7 @@ This rule has a single option, which can either be a string or an object. * `"always"` requires line breaks inside all function parentheses. * `"never"` disallows line breaks inside all function parentheses. * `"multiline"` (default) requires linebreaks inside function parentheses if any of the parameters/arguments have a line break between them. Otherwise, it disallows linebreaks. +* `"multiline-arguments"` works like `multiline` but allows linebreaks inside function parentheses if there is only one parameter/argument. * `"consistent"` requires consistent usage of linebreaks for each pair of parentheses. It reports an error if one parenthesis in the pair has a linebreak inside it and the other parenthesis does not. * `{ "minItems": value }` requires linebreaks inside function parentheses if the number of parameters/arguments is at least `value`. Otherwise, it disallows linebreaks. @@ -225,6 +226,56 @@ foo( ); ``` +Examples of **incorrect** code for this rule with the `"multiline-arguments"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline-arguments"] */ + +function foo(bar, + baz +) {} + +var foo = function(bar, + baz +) {}; + +var foo = ( + bar, + baz) => {}; + +foo( + bar, + baz); + +foo( + bar, qux, + baz +); +``` + +Examples of **correct** code for this rule with the consistent `"multiline-arguments"` option: + +```js +/* eslint function-paren-newline: ["error", "multiline-arguments"] */ + +function foo( + bar, + baz +) {} + +var foo = function(bar, baz) {}; + +var foo = ( + bar +) => {}; + +foo( + function() { + return baz; + } +); +``` + Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option: ```js diff --git a/lib/rules/function-paren-newline.js b/lib/rules/function-paren-newline.js index 37256484f4a..c4775fb19af 100644 --- a/lib/rules/function-paren-newline.js +++ b/lib/rules/function-paren-newline.js @@ -31,7 +31,7 @@ module.exports = { { oneOf: [ { - enum: ["always", "never", "consistent", "multiline"] + enum: ["always", "never", "consistent", "multiline", "multiline-arguments"] }, { type: "object", @@ -50,6 +50,7 @@ module.exports = { messages: { expectedBefore: "Expected newline before ')'.", expectedAfter: "Expected newline after '('.", + expectedBetween: "Expected newline between arguments/params.", unexpectedBefore: "Unexpected newline before '('.", unexpectedAfter: "Unexpected newline after ')'." } @@ -59,6 +60,7 @@ module.exports = { const sourceCode = context.getSourceCode(); const rawOption = context.options[0] || "multiline"; const multilineOption = rawOption === "multiline"; + const multilineArgumentsOption = rawOption === "multiline-arguments"; const consistentOption = rawOption === "consistent"; let minItems; @@ -83,7 +85,10 @@ module.exports = { * @returns {boolean} `true` if there should be newlines inside the function parens */ function shouldHaveNewlines(elements, hasLeftNewline) { - if (multilineOption) { + if (multilineArgumentsOption && elements.length === 1) { + return hasLeftNewline; + } + if (multilineOption || multilineArgumentsOption) { return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line); } if (consistentOption) { @@ -93,7 +98,7 @@ module.exports = { } /** - * Validates a list of arguments or parameters + * Validates parens * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token * @param {ASTNode[]} elements The arguments or parameters in the list * @returns {void} @@ -148,6 +153,33 @@ module.exports = { } } + /** + * Validates a list of arguments or parameters + * @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token + * @param {ASTNode[]} elements The arguments or parameters in the list + * @returns {void} + */ + function validateArguments(parens, elements) { + const leftParen = parens.leftParen; + const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen); + const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen); + const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline); + + for (let i = 0; i <= elements.length - 2; i++) { + const currentElement = elements[i]; + const nextElement = elements[i + 1]; + const hasNewLine = currentElement.loc.end.line !== nextElement.loc.start.line; + + if (!hasNewLine && needsNewlines) { + context.report({ + node: currentElement, + messageId: "expectedBetween", + fix: fixer => fixer.insertTextBefore(nextElement, "\n") + }); + } + } + } + /** * Gets the left paren and right paren tokens of a node. * @param {ASTNode} node The node with parens @@ -215,6 +247,10 @@ module.exports = { if (parens) { validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments); + + if (multilineArgumentsOption) { + validateArguments(parens, astUtils.isFunction(node) ? node.params : node.arguments); + } } } diff --git a/tests/lib/rules/function-paren-newline.js b/tests/lib/rules/function-paren-newline.js index 5a062a1bf0b..52218273417 100644 --- a/tests/lib/rules/function-paren-newline.js +++ b/tests/lib/rules/function-paren-newline.js @@ -20,6 +20,7 @@ const LEFT_MISSING_ERROR = { messageId: "expectedAfter", type: "Punctuator" }; const LEFT_UNEXPECTED_ERROR = { messageId: "unexpectedAfter", type: "Punctuator" }; const RIGHT_MISSING_ERROR = { messageId: "expectedBefore", type: "Punctuator" }; const RIGHT_UNEXPECTED_ERROR = { messageId: "unexpectedBefore", type: "Punctuator" }; +const EXPECTED_BETWEEN = { messageId: "expectedBetween", type: "Identifier" }; const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } }); @@ -82,12 +83,182 @@ ruleTester.run("function-paren-newline", rule, { return value; }) `, - - // always option { code: "function baz(foo, bar) {}", options: ["multiline"] }, + + // multiline-arguments + { + code: "function baz(foo, bar) {}", + options: ["multiline-arguments"] + }, + { + code: "function baz(foo) {}", + options: ["multiline-arguments"] + }, + { + code: "(function(foo, bar) {});", + options: ["multiline-arguments"] + }, + { + code: "(function(foo) {});", + options: ["multiline-arguments"] + }, + { + code: "(function baz(foo, bar) {});", + options: ["multiline-arguments"] + }, + { + code: "(function baz(foo) {});", + options: ["multiline-arguments"] + }, + { + code: "(foo, bar) => {};", + options: ["multiline-arguments"] + }, + { + code: "foo => {};", + options: ["multiline-arguments"] + }, + { + code: "baz(foo, bar);", + options: ["multiline-arguments"] + }, + { + code: "baz(foo);", + options: ["multiline-arguments"] + }, + { + code: "function baz() {}", + options: ["multiline-arguments"] + }, + { + code: ` + function baz( + foo, + bar + ) {} + `, + options: ["multiline-arguments"] + }, + { + code: ` + function baz( + foo + ) {} + `, + options: ["multiline-arguments"] + }, + { + code: ` + (function( + foo, + bar + ) {}); + `, + options: ["multiline-arguments"] + }, + { + code: ` + (function( + foo + ) {}); + `, + options: ["multiline-arguments"] + }, + { + code: ` + (function baz( + foo, + bar + ) {}); + `, + options: ["multiline-arguments"] + }, + { + code: ` + (function baz( + foo + ) {}); + `, + options: ["multiline-arguments"] + }, + { + code: ` + ( + foo, + bar + ) => {}; + `, + options: ["multiline-arguments"] + }, + { + code: ` + ( + foo + ) => {}; + `, + options: ["multiline-arguments"] + }, + { + code: ` + baz( + foo, + bar + ); + `, + options: ["multiline-arguments"] + }, + { + code: ` + baz( + foo + ); + `, + options: ["multiline-arguments"] + }, + { + code: ` + baz(\`foo + bar\`) + `, + options: ["multiline-arguments"] + }, + { + code: "new Foo(bar, baz)", + options: ["multiline-arguments"] + }, + { + code: "new Foo(bar)", + options: ["multiline-arguments"] + }, + { + code: "new Foo", + options: ["multiline-arguments"] + }, + { + code: "new (Foo)", + options: ["multiline-arguments"] + }, + + { + code: ` + (foo) + (bar) + `, + options: ["multiline-arguments"] + }, + { + code: ` + foo.map(value => { + return value; + }) + `, + options: ["multiline-arguments"] + }, + + // always option { code: ` function baz( @@ -344,6 +515,193 @@ ruleTester.run("function-paren-newline", rule, { errors: [RIGHT_UNEXPECTED_ERROR] }, + // multiline-arguments + { + code: ` + function baz(foo, + bar + ) {} + `, + output: ` + function baz(\nfoo, + bar + ) {} + `, + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR] + }, + { + code: ` + (function( + foo, + bar) {}) + `, + output: ` + (function( + foo, + bar\n) {}) + `, + options: ["multiline-arguments"], + errors: [RIGHT_MISSING_ERROR] + }, + { + code: ` + (function baz(foo, + bar) {}) + `, + output: ` + (function baz(\nfoo, + bar\n) {}) + `, + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] + }, + { + code: ` + baz( + foo, bar); + `, + output: ` + baz(foo, bar); + `, + options: ["multiline-arguments"], + errors: [LEFT_UNEXPECTED_ERROR] + }, + { + code: ` + (foo, bar + ) => {}; + `, + output: ` + (foo, bar) => {}; + `, + options: ["multiline-arguments"], + errors: [RIGHT_UNEXPECTED_ERROR] + }, + { + code: ` + function baz( + foo, bar + ) {} + `, + output: ` + function baz(foo, bar) {} + `, + options: ["multiline-arguments"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] + }, + { + code: ` + function baz( + ) {} + `, + output: ` + function baz() {} + `, + options: ["multiline-arguments"], + errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR] + }, + { + code: ` + new Foo(bar, + baz); + `, + output: ` + new Foo(\nbar, + baz\n); + `, + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR] + }, + { + code: ` + function baz(/* not fixed due to comment */ + foo) {} + `, + output: ` + function baz(/* not fixed due to comment */ + foo\n) {} + `, + options: ["multiline-arguments"], + errors: [RIGHT_MISSING_ERROR] + }, + { + code: ` + function baz(foo + /* not fixed due to comment */) {} + `, + output: null, + options: ["multiline-arguments"], + errors: [RIGHT_UNEXPECTED_ERROR] + }, + { + code: ` + function baz( + qwe, + foo, bar + ) {} + `, + output: ` + function baz( + qwe, + foo, \nbar + ) {} + `, + options: ["multiline-arguments"], + errors: [EXPECTED_BETWEEN] + }, + { + code: ` + function baz( + qwe, foo, + bar + ) {} + `, + output: ` + function baz( + qwe, \nfoo, + bar + ) {} + `, + options: ["multiline-arguments"], + errors: [EXPECTED_BETWEEN] + }, + { + code: ` + function baz(qwe, foo, + bar) {} + `, + output: ` + function baz(\nqwe, \nfoo, + bar\n) {} + `, + options: ["multiline-arguments"], + errors: [LEFT_MISSING_ERROR, EXPECTED_BETWEEN, RIGHT_MISSING_ERROR] + }, + { + code: ` + baz( + foo); + `, + output: ` + baz( + foo\n); + `, + options: ["multiline-arguments"], + errors: [RIGHT_MISSING_ERROR] + }, + { + code: ` + baz(foo + ); + `, + output: ` + baz(foo); + `, + options: ["multiline-arguments"], + errors: [RIGHT_UNEXPECTED_ERROR] + }, + // always option { code: `