Repository: eslint/eslint
Pull request #9198 (2017-09-01 22:06:54)

Docs: fix link format in prefer-arrow-callback.md
**What is the purpose of this pull request? (put an "X" next to item)**

[x] Documentation update

**What changes did you make? (Give an overview)**

Fix a currently unrendered link.

**Is there anything you'd like reviewers to focus on?**

No.



eslintbot: LGTM

Patch:
diff --git a/Makefile.js b/Makefile.js
index 0c1424aa6..0d397871c 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -937,7 +937,8 @@ target.checkLicenses = function() {
 
         if (impermissible.length) {
             impermissible.forEach(dependency => {
-                console.error("%s license for %s is impermissible.",
+                console.error(
+                    "%s license for %s is impermissible.",
                     dependency.licenses,
                     dependency.name
                 );
diff --git a/bin/eslint.js b/bin/eslint.js
index 10a7f73da..1a298047a 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -43,9 +43,10 @@ process.once("uncaughtException", err => {
 
     if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
         const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8"));
+        const pkg = require("../package.json");
 
         console.error("\nOops! Something went wrong! :(");
-        console.error(`\n${template(err.messageData || {})}`);
+        console.error(`\nESLint: ${pkg.version}.\n${template(err.messageData || {})}`);
     } else {
 
         console.error(err.message);
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 35c8acdbe..a6fc9adf5 100755
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -44,6 +44,7 @@ module.exports = {
         "func-name-matching": "off",
         "func-names": "off",
         "func-style": "off",
+        "function-paren-newline": "off",
         "generator-star-spacing": "off",
         "getter-return": "off",
         "global-require": "off",
diff --git a/docs/rules/function-paren-newline.md b/docs/rules/function-paren-newline.md
new file mode 100644
index 000000000..f45783a42
--- /dev/null
+++ b/docs/rules/function-paren-newline.md
@@ -0,0 +1,277 @@
+# enforce consistent line breaks inside function parentheses (function-paren-newline)
+
+Many styleguides require or disallow newlines inside of function parentheses.
+
+## Rule Details
+
+This rule enforces consistent line breaks inside parentheses of function parameters or arguments.
+
+### Options
+
+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.
+* `"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.
+
+Example configurations:
+
+```json
+{
+  "rules": {
+    "function-paren-newline": ["error", "never"]
+  }
+}
+```
+
+```json
+{
+  "rules": {
+    "function-paren-newline": ["error", { "minItems": 3 }]
+  }
+}
+```
+
+Examples of **incorrect** code for this rule with the `"always"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "always"] */
+
+function foo(bar, baz) {}
+
+var foo = function(bar, baz) {};
+
+var foo = (bar, baz) => {};
+
+foo(bar, baz);
+```
+
+Examples of **correct** code for this rule with the `"always"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "always"] */
+
+function foo(
+  bar,
+  baz
+) {}
+
+var foo = function(
+  bar, baz
+) {};
+
+var foo = (
+  bar,
+  baz
+) => {};
+
+foo(
+  bar,
+  baz
+);
+```
+
+Examples of **incorrect** code for this rule with the `"never"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "never"] */
+
+function foo(
+  bar,
+  baz
+) {}
+
+var foo = function(
+  bar, baz
+) {};
+
+var foo = (
+  bar,
+  baz
+) => {};
+
+foo(
+  bar,
+  baz
+);
+```
+
+Examples of **correct** code for this rule with the `"never"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "never"] */
+
+function foo(bar, baz) {}
+
+function foo(bar,
+             baz) {}
+
+var foo = function(bar, baz) {};
+
+var foo = (bar, baz) => {};
+
+foo(bar, baz);
+
+foo(bar,
+  baz);
+```
+
+Examples of **incorrect** code for this rule with the default `"multiline"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "multiline"] */
+
+function foo(bar,
+  baz
+) {}
+
+var foo = function(
+  bar, baz
+) {};
+
+var foo = (
+  bar,
+  baz) => {};
+
+foo(bar,
+  baz);
+
+foo(
+  function() {
+    return baz;
+  }
+);
+```
+
+Examples of **correct** code for this rule with the default `"multiline"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "multiline"] */
+
+function foo(bar, baz) {}
+
+var foo = function(
+  bar,
+  baz
+) {};
+
+var foo = (bar, baz) => {};
+
+foo(bar, baz, qux);
+
+foo(
+  bar,
+  baz,
+  qux
+);
+
+foo(function() {
+  return baz;
+});
+```
+
+Examples of **incorrect** code for this rule with the `"consistent"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "consistent"] */
+
+function foo(bar,
+  baz
+) {}
+
+var foo = function(bar,
+  baz
+) {};
+
+var foo = (
+  bar,
+  baz) => {};
+
+foo(
+  bar,
+  baz);
+
+foo(
+  function() {
+    return baz;
+  });
+```
+
+Examples of **correct** code for this rule with the consistent `"consistent"` option:
+
+```js
+/* eslint function-paren-newline: ["error", "consistent"] */
+
+function foo(bar,
+  baz) {}
+
+var foo = function(bar, baz) {};
+
+var foo = (
+  bar,
+  baz
+) => {};
+
+foo(
+  bar, baz
+);
+
+foo(
+  function() {
+    return baz;
+  }
+);
+```
+
+Examples of **incorrect** code for this rule with the `{ "minItems": 3 }` option:
+
+```js
+/* eslint function-paren-newline: ["error", { "minItems": 3 }] */
+
+function foo(
+  bar,
+  baz
+) {}
+
+function foo(bar, baz, qux) {}
+
+var foo = function(
+  bar, baz
+) {};
+
+var foo = (bar,
+  baz) => {};
+
+foo(bar,
+  baz);
+```
+
+Examples of **correct** code for this rule with the `{ "minItems": 3 }` option:
+
+```js
+/* eslint function-paren-newline: ["error", { "minItems": 3 }] */
+
+function foo(bar, baz) {}
+
+var foo = function(
+  bar,
+  baz,
+  qux
+) {};
+
+var foo = (
+  bar, baz, qux
+) => {};
+
+foo(bar, baz);
+
+foo(
+  bar, baz, qux
+);
+```
+
+## When Not To Use It
+
+If don't want to enforce consistent linebreaks inside function parentheses, do not turn on this rule.
diff --git a/docs/rules/prefer-arrow-callback.md b/docs/rules/prefer-arrow-callback.md
index 3335e73a2..95ef16b3a 100644
--- a/docs/rules/prefer-arrow-callback.md
+++ b/docs/rules/prefer-arrow-callback.md
@@ -97,4 +97,4 @@ someArray.map(function(itm) { return this.doSomething(itm); }, someObject);
 
 ## Further Reading
 
-- [More on ES6 arrow functions]('https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions')
+- [More on ES6 arrow functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions)
diff --git a/lib/code-path-analysis/code-path-analyzer.js b/lib/code-path-analysis/code-path-analyzer.js
index 539b5e18b..899f240bc 100644
--- a/lib/code-path-analysis/code-path-analyzer.js
+++ b/lib/code-path-analysis/code-path-analyzer.js
@@ -154,7 +154,8 @@ function forwardCurrentToHead(analyzer, node) {
                 analyzer.emitter.emit(
                     "onCodePathSegmentEnd",
                     currentSegment,
-                    node);
+                    node
+                );
             }
         }
     }
@@ -175,7 +176,8 @@ function forwardCurrentToHead(analyzer, node) {
                 analyzer.emitter.emit(
                     "onCodePathSegmentStart",
                     headSegment,
-                    node);
+                    node
+                );
             }
         }
     }
@@ -202,7 +204,8 @@ function leaveFromCurrentSegment(analyzer, node) {
             analyzer.emitter.emit(
                 "onCodePathSegmentEnd",
                 currentSegment,
-                node);
+                node
+            );
         }
     }
 
@@ -369,7 +372,8 @@ function processCodePathToEnter(analyzer, node) {
         case "SwitchStatement":
             state.pushSwitchContext(
                 node.cases.some(isCaseNode),
-                astUtils.getLabel(node));
+                astUtils.getLabel(node)
+            );
             break;
 
         case "TryStatement":
diff --git a/lib/code-path-analysis/code-path-segment.js b/lib/code-path-analysis/code-path-segment.js
index db1eba456..825c9b74b 100644
--- a/lib/code-path-analysis/code-path-segment.js
+++ b/lib/code-path-analysis/code-path-segment.js
@@ -164,7 +164,8 @@ class CodePathSegment {
         return new CodePathSegment(
             id,
             flattenUnusedSegments(allPrevSegments),
-            allPrevSegments.some(isReachable));
+            allPrevSegments.some(isReachable)
+        );
     }
 
     /**
diff --git a/lib/code-path-analysis/code-path-state.js b/lib/code-path-analysis/code-path-state.js
index 7c8abb207..7d9ee55ab 100644
--- a/lib/code-path-analysis/code-path-state.js
+++ b/lib/code-path-analysis/code-path-state.js
@@ -855,9 +855,12 @@ class CodePathState {
                 prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]);
             }
 
-            segments.push(CodePathSegment.newNext(
-                this.idGenerator.next(),
-                prevSegsOfLeavingSegment));
+            segments.push(
+                CodePathSegment.newNext(
+                    this.idGenerator.next(),
+                    prevSegsOfLeavingSegment
+                )
+            );
         }
 
         this.pushForkContext(true);
@@ -992,7 +995,8 @@ class CodePathState {
                 makeLooped(
                     this,
                     forkContext.head,
-                    context.continueDestSegments);
+                    context.continueDestSegments
+                );
                 break;
 
             case "DoWhileStatement": {
@@ -1013,7 +1017,8 @@ class CodePathState {
                     makeLooped(
                         this,
                         segmentsList[i],
-                        context.entrySegments);
+                        context.entrySegments
+                    );
                 }
                 break;
             }
@@ -1024,7 +1029,8 @@ class CodePathState {
                 makeLooped(
                     this,
                     forkContext.head,
-                    context.leftSegments);
+                    context.leftSegments
+                );
                 break;
 
             /* istanbul ignore next */
@@ -1149,7 +1155,8 @@ class CodePathState {
             finalizeTestSegmentsOfFor(
                 context,
                 choiceContext,
-                forkContext.head);
+                forkContext.head
+            );
         } else {
             context.endOfInitSegments = forkContext.head;
         }
@@ -1180,13 +1187,15 @@ class CodePathState {
                 makeLooped(
                     this,
                     context.endOfUpdateSegments,
-                    context.testSegments);
+                    context.testSegments
+                );
             }
         } else if (context.testSegments) {
             finalizeTestSegmentsOfFor(
                 context,
                 choiceContext,
-                forkContext.head);
+                forkContext.head
+            );
         } else {
             context.endOfInitSegments = forkContext.head;
         }
diff --git a/lib/code-path-analysis/code-path.js b/lib/code-path-analysis/code-path.js
index 6ef07b4a2..5fc5d22b0 100644
--- a/lib/code-path-analysis/code-path.js
+++ b/lib/code-path-analysis/code-path.js
@@ -51,7 +51,8 @@ class CodePath {
         Object.defineProperty(
             this,
             "internal",
-            { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) });
+            { value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
+        );
 
         // Adds this into `childCodePaths` of `upper`.
         if (upper) {
diff --git a/lib/code-path-analysis/fork-context.js b/lib/code-path-analysis/fork-context.js
index 7423c1319..4fae6bbb1 100644
--- a/lib/code-path-analysis/fork-context.js
+++ b/lib/code-path-analysis/fork-context.js
@@ -254,7 +254,8 @@ class ForkContext {
         return new ForkContext(
             parentContext.idGenerator,
             parentContext,
-            (forkLeavingPath ? 2 : 1) * parentContext.count);
+            (forkLeavingPath ? 2 : 1) * parentContext.count
+        );
     }
 }
 
diff --git a/lib/linter.js b/lib/linter.js
index c0ae742d2..7e32eedb9 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -989,9 +989,10 @@ class Linter {
 
                 // add all the selectors from the rule as listeners
                 Object.keys(rule).forEach(selector => {
-                    emitter.on(selector, timing.enabled
-                        ? timing.time(ruleId, rule[selector])
-                        : rule[selector]
+                    emitter.on(
+                        selector, timing.enabled
+                            ? timing.time(ruleId, rule[selector])
+                            : rule[selector]
                     );
                 });
             } catch (ex) {
diff --git a/lib/rules/function-paren-newline.js b/lib/rules/function-paren-newline.js
new file mode 100644
index 000000000..10ad960a4
--- /dev/null
+++ b/lib/rules/function-paren-newline.js
@@ -0,0 +1,221 @@
+/**
+ * @fileoverview enforce consistent line breaks inside function parentheses
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "enforce consistent line breaks inside function parentheses",
+            category: "Stylistic Issues",
+            recommended: false
+        },
+        fixable: "whitespace",
+        schema: [
+            {
+                oneOf: [
+                    {
+                        enum: ["always", "never", "consistent", "multiline"]
+                    },
+                    {
+                        type: "object",
+                        properties: {
+                            minItems: {
+                                type: "integer",
+                                minimum: 0
+                            }
+                        },
+                        additionalProperties: false
+                    }
+                ]
+            }
+        ]
+    },
+
+    create(context) {
+        const sourceCode = context.getSourceCode();
+        const rawOption = context.options[0] || "multiline";
+        const multilineOption = rawOption === "multiline";
+        const consistentOption = rawOption === "consistent";
+        let minItems;
+
+        if (typeof rawOption === "object") {
+            minItems = rawOption.minItems;
+        } else if (rawOption === "always") {
+            minItems = 0;
+        } else if (rawOption === "never") {
+            minItems = Infinity;
+        } else {
+            minItems = null;
+        }
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        /**
+         * Determines whether there should be newlines inside function parens
+         * @param {ASTNode[]} elements The arguments or parameters in the list
+         * @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
+         * @returns {boolean} `true` if there should be newlines inside the function parens
+         */
+        function shouldHaveNewlines(elements, hasLeftNewline) {
+            if (multilineOption) {
+                return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
+            }
+            if (consistentOption) {
+                return hasLeftNewline;
+            }
+            return elements.length >= minItems;
+        }
+
+        /**
+         * 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 validateParens(parens, elements) {
+            const leftParen = parens.leftParen;
+            const rightParen = parens.rightParen;
+            const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
+            const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
+            const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
+            const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
+            const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
+
+            if (hasLeftNewline && !needsNewlines) {
+                context.report({
+                    node: leftParen,
+                    message: "Unexpected newline after '('.",
+                    fix(fixer) {
+                        return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
+
+                            // If there is a comment between the ( and the first element, don't do a fix.
+                            ? null
+                            : fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
+                    }
+                });
+            } else if (!hasLeftNewline && needsNewlines) {
+                context.report({
+                    node: leftParen,
+                    message: "Expected a newline after '('.",
+                    fix: fixer => fixer.insertTextAfter(leftParen, "\n")
+                });
+            }
+
+            if (hasRightNewline && !needsNewlines) {
+                context.report({
+                    node: rightParen,
+                    message: "Unexpected newline before ')'.",
+                    fix(fixer) {
+                        return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
+
+                            // If there is a comment between the last element and the ), don't do a fix.
+                            ? null
+                            : fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
+                    }
+                });
+            } else if (!hasRightNewline && needsNewlines) {
+                context.report({
+                    node: rightParen,
+                    message: "Expected a newline before ')'.",
+                    fix: fixer => fixer.insertTextBefore(rightParen, "\n")
+                });
+            }
+        }
+
+        /**
+         * Gets the left paren and right paren tokens of a node.
+         * @param {ASTNode} node The node with parens
+         * @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
+         * Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
+         * with a single parameter)
+         */
+        function getParenTokens(node) {
+            switch (node.type) {
+                case "NewExpression":
+                    if (!node.arguments.length && !(
+                        astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
+                        astUtils.isClosingParenToken(sourceCode.getLastToken(node))
+                    )) {
+
+                        // If the NewExpression does not have parens (e.g. `new Foo`), return null.
+                        return null;
+                    }
+
+                    // falls through
+
+                case "CallExpression":
+                    return {
+                        leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken),
+                        rightParen: sourceCode.getLastToken(node)
+                    };
+
+                case "FunctionDeclaration":
+                case "FunctionExpression": {
+                    const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
+                    const rightParen = node.params.length
+                        ? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
+                        : sourceCode.getTokenAfter(leftParen);
+
+                    return { leftParen, rightParen };
+                }
+
+                case "ArrowFunctionExpression": {
+                    const firstToken = sourceCode.getFirstToken(node);
+
+                    if (!astUtils.isOpeningParenToken(firstToken)) {
+
+                        // If the ArrowFunctionExpression has a single param without parens, return null.
+                        return null;
+                    }
+
+                    return {
+                        leftParen: firstToken,
+                        rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken)
+                    };
+                }
+
+                default:
+                    throw new TypeError(`unexpected node with type ${node.type}`);
+            }
+        }
+
+        /**
+         * Validates the parentheses for a node
+         * @param {ASTNode} node The node with parens
+         * @returns {void}
+         */
+        function validateNode(node) {
+            const parens = getParenTokens(node);
+
+            if (parens) {
+                validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments);
+            }
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        return {
+            ArrowFunctionExpression: validateNode,
+            CallExpression: validateNode,
+            FunctionDeclaration: validateNode,
+            FunctionExpression: validateNode,
+            NewExpression: validateNode
+        };
+    }
+};
diff --git a/lib/rules/indent-legacy.js b/lib/rules/indent-legacy.js
index 89d98f833..301217e4a 100644
--- a/lib/rules/indent-legacy.js
+++ b/lib/rules/indent-legacy.js
@@ -965,7 +965,8 @@ module.exports = {
             const regex = /^return\s*?\(\s*?\);*?/;
 
             const statementWithoutArgument = sourceCode.getText(node).replace(
-                sourceCode.getText(node.argument), "");
+                sourceCode.getText(node.argument), ""
+            );
 
             return regex.test(statementWithoutArgument);
         }
diff --git a/lib/rules/no-invalid-this.js b/lib/rules/no-invalid-this.js
index 64ef4882e..5a0a62f7a 100644
--- a/lib/rules/no-invalid-this.js
+++ b/lib/rules/no-invalid-this.js
@@ -46,7 +46,8 @@ module.exports = {
                 current.init = true;
                 current.valid = !astUtils.isDefaultThisBinding(
                     current.node,
-                    sourceCode);
+                    sourceCode
+                );
             }
             return current;
         };
diff --git a/lib/timing.js b/lib/timing.js
index 15c93800f..e33ac8f45 100644
--- a/lib/timing.js
+++ b/lib/timing.js
@@ -84,11 +84,11 @@ function display(data) {
         }
     });
 
-    const table = rows.map(row =>
+    const table = rows.map(row => (
         row
             .map((cell, index) => ALIGN[index](cell, widths[index]))
             .join(" | ")
-    );
+    ));
 
     table.splice(1, 0, widths.map((w, index) => {
         if (index !== 0 && index !== widths.length - 1) {
diff --git a/lib/util/fix-tracker.js b/lib/util/fix-tracker.js
index 189072e1a..067070df0 100644
--- a/lib/util/fix-tracker.js
+++ b/lib/util/fix-tracker.js
@@ -57,8 +57,7 @@ class FixTracker {
     retainEnclosingFunction(node) {
         const functionNode = astUtils.getUpperFunction(node);
 
-        return this.retainRange(
-            functionNode ? functionNode.range : this.sourceCode.ast.range);
+        return this.retainRange(functionNode ? functionNode.range : this.sourceCode.ast.range);
     }
 
     /**
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index 410ede81b..2af0a614f 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -27,6 +27,7 @@ rules:
     for-direction: "error"
     func-call-spacing: "error"
     func-style: ["error", "declaration"]
+    function-paren-newline: ["error", "consistent"]
     generator-star-spacing: "error"
     getter-return: "error"
     guard-for-in: "error"


Test patch:
diff --git a/tests/lib/config.js b/tests/lib/config.js
index 4b39fe82c..f44cc5985 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -212,7 +212,8 @@ describe("Config", () => {
             const configHelper = new Config({}, linter),
                 expected = getFakeFixturePath("broken", ".eslintrc"),
                 actual = Array.from(
-                    configHelper.findLocalConfigFiles(getFakeFixturePath("broken")));
+                    configHelper.findLocalConfigFiles(getFakeFixturePath("broken"))
+                );
 
             assert.equal(actual[0], expected);
         });
@@ -220,7 +221,8 @@ describe("Config", () => {
         it("should return an empty array when an .eslintrc file is not found", () => {
             const configHelper = new Config({}, linter),
                 actual = Array.from(
-                    configHelper.findLocalConfigFiles(getFakeFixturePath()));
+                    configHelper.findLocalConfigFiles(getFakeFixturePath())
+                );
 
             assert.isArray(actual);
             assert.lengthOf(actual, 0);
@@ -231,7 +233,8 @@ describe("Config", () => {
                 expected0 = getFakeFixturePath("packagejson", "subdir", "package.json"),
                 expected1 = getFakeFixturePath("packagejson", ".eslintrc"),
                 actual = Array.from(
-                    configHelper.findLocalConfigFiles(getFakeFixturePath("packagejson", "subdir")));
+                    configHelper.findLocalConfigFiles(getFakeFixturePath("packagejson", "subdir"))
+                );
 
             assert.equal(actual[0], expected0);
             assert.equal(actual[1], expected1);
@@ -243,7 +246,8 @@ describe("Config", () => {
 
                 // The first element of the array is the .eslintrc in the same directory.
                 actual = Array.from(
-                    configHelper.findLocalConfigFiles(getFakeFixturePath("broken")));
+                    configHelper.findLocalConfigFiles(getFakeFixturePath("broken"))
+                );
 
             assert.equal(actual.length, 1);
             assert.equal(actual, expected);
@@ -258,7 +262,8 @@ describe("Config", () => {
                 ],
 
                 actual = Array.from(
-                    configHelper.findLocalConfigFiles(getFakeFixturePath("fileexts/subdir/subsubdir")));
+                    configHelper.findLocalConfigFiles(getFakeFixturePath("fileexts/subdir/subsubdir"))
+                );
 
 
             assert.deepEqual(actual.length, expected.length);
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index 6fc24f0ad..c6fec97ae 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -104,7 +104,8 @@ describe("Validator", () => {
         });
 
         it("should do nothing with a valid eslint config", () => {
-            const fn = validator.validate.bind(null,
+            const fn = validator.validate.bind(
+                null,
                 {
                     root: true,
                     globals: { globalFoo: "bar" },
@@ -125,7 +126,8 @@ describe("Validator", () => {
         });
 
         it("should throw with an unknown property", () => {
-            const fn = validator.validate.bind(null,
+            const fn = validator.validate.bind(
+                null,
                 {
                     foo: true
                 },
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index f09eab1d7..829809650 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -159,9 +159,9 @@ describe("IgnoredPaths", () => {
             });
 
             assert.ok(
-                ignorePattern.every(pattern =>
+                ignorePattern.every(pattern => (
                     getIgnoreRules(ignoredPaths).some(rule => rule.pattern === pattern)
-                )
+                ))
             );
         });
 
diff --git a/tests/lib/report-translator.js b/tests/lib/report-translator.js
index 3e04658ba..ea8d97633 100644
--- a/tests/lib/report-translator.js
+++ b/tests/lib/report-translator.js
@@ -36,7 +36,8 @@ describe("createReportTranslator", () => {
                     tokens: true,
                     comment: true
                 }
-            ));
+            )
+        );
     }
 
     let node, location, message, translateReport;
diff --git a/tests/lib/rules/function-paren-newline.js b/tests/lib/rules/function-paren-newline.js
new file mode 100644
index 000000000..aa60ff9f9
--- /dev/null
+++ b/tests/lib/rules/function-paren-newline.js
@@ -0,0 +1,601 @@
+/**
+ * @fileoverview enforce consistent line breaks inside function parentheses
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/function-paren-newline");
+const RuleTester = require("../../../lib/testers/rule-tester");
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const LEFT_MISSING_ERROR = { message: "Expected a newline after '('.", type: "Punctuator" };
+const LEFT_UNEXPECTED_ERROR = { message: "Unexpected newline after '('.", type: "Punctuator" };
+const RIGHT_MISSING_ERROR = { message: "Expected a newline before ')'.", type: "Punctuator" };
+const RIGHT_UNEXPECTED_ERROR = { message: "Unexpected newline before ')'.", type: "Punctuator" };
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+ruleTester.run("function-paren-newline", rule, {
+
+    valid: [
+
+        // multiline option (default)
+        "function baz(foo, bar) {}",
+        "(function(foo, bar) {});",
+        "(function baz(foo, bar) {});",
+        "(foo, bar) => {};",
+        "foo => {};",
+        "baz(foo, bar);",
+        "function baz() {}",
+        `
+            function baz(
+                foo,
+                bar
+            ) {}
+        `,
+        `
+            (function(
+                foo,
+                bar
+            ) {});
+        `,
+        `
+            (function baz(
+                foo,
+                bar
+            ) {});
+        `,
+        `
+            (
+                foo,
+                bar
+            ) => {};
+        `,
+        `
+            baz(
+                foo,
+                bar
+            );
+        `,
+        `
+            baz(\`foo
+                bar\`)
+        `,
+        "new Foo(bar, baz)",
+        "new Foo",
+        "new (Foo)",
+
+        `
+            (foo)
+            (bar)
+        `,
+        `
+            foo.map(value => {
+              return value;
+            })
+        `,
+
+        // always option
+        {
+            code: "function baz(foo, bar) {}",
+            options: ["multiline"]
+        },
+        {
+            code: `
+                function baz(
+                    foo,
+                    bar
+                ) {}
+            `,
+            options: ["always"]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar
+                ) {});
+            `,
+            options: ["always"]
+        },
+        {
+            code: `
+                (function baz(
+                    foo,
+                    bar
+                ) {});
+            `,
+            options: ["always"]
+        },
+        {
+            code: `
+                (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            options: ["always"]
+        },
+        {
+            code: `
+                baz(
+                    foo,
+                    bar
+                );
+            `,
+            options: ["always"]
+        },
+        {
+            code: `
+                function baz(
+                ) {}
+            `,
+            options: ["always"]
+        },
+
+        // never option
+        {
+            code: "function baz(foo, bar) {}",
+            options: ["never"]
+        },
+        {
+            code: "(function(foo, bar) {});",
+            options: ["never"]
+        },
+        {
+            code: "(function baz(foo, bar) {});",
+            options: ["never"]
+        },
+        {
+            code: "(foo, bar) => {};",
+            options: ["never"]
+        },
+        {
+            code: "baz(foo, bar);",
+            options: ["never"]
+        },
+        {
+            code: "function baz() {}",
+            options: ["never"]
+        },
+
+        // minItems option
+        {
+            code: "function baz(foo, bar) {}",
+            options: [{ minItems: 3 }]
+        },
+        {
+            code: `
+                function baz(
+                    foo, bar, qux
+                ) {}
+            `,
+            options: [{ minItems: 3 }]
+        },
+        {
+            code: `
+                baz(
+                    foo, bar, qux
+                );
+            `,
+            options: [{ minItems: 3 }]
+        },
+        {
+            code: "baz(foo, bar);",
+            options: [{ minItems: 3 }]
+        },
+        {
+            code: "foo(bar, baz)",
+            options: ["consistent"]
+        },
+        {
+            code: `
+                foo(bar,
+                baz)
+            `,
+            options: ["consistent"]
+        },
+        {
+            code: `
+                foo(
+                    bar, baz
+                )
+            `,
+            options: ["consistent"]
+        },
+        {
+            code: `
+                foo(
+                    bar,
+                    baz
+                )
+            `,
+            options: ["consistent"]
+        }
+    ],
+
+    invalid: [
+
+        // multiline option (default)
+        {
+            code: `
+                function baz(foo,
+                    bar
+                ) {}
+            `,
+            output: `
+                function baz(\nfoo,
+                    bar
+                ) {}
+            `,
+            errors: [LEFT_MISSING_ERROR]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar) {})
+            `,
+            output: `
+                (function(
+                    foo,
+                    bar\n) {})
+            `,
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                (function baz(foo,
+                    bar) {})
+            `,
+            output: `
+                (function baz(\nfoo,
+                    bar\n) {})
+            `,
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                baz(
+                    foo, bar);
+            `,
+            output: `
+                baz(foo, bar);
+            `,
+            errors: [LEFT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                (foo, bar
+                ) => {};
+            `,
+            output: `
+                (foo, bar) => {};
+            `,
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                    foo, bar
+                ) {}
+            `,
+            output: `
+                function baz(foo, bar) {}
+            `,
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                    foo =
+                    1
+                ) {}
+            `,
+            output: `
+                function baz(foo =
+                    1) {}
+            `,
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                ) {}
+            `,
+            output: `
+                function baz() {}
+            `,
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                new Foo(bar,
+                    baz);
+            `,
+            output: `
+                new Foo(\nbar,
+                    baz\n);
+            `,
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                function baz(/* not fixed due to comment */
+                foo) {}
+            `,
+            output: null,
+            errors: [LEFT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(foo
+                /* not fixed due to comment */) {}
+            `,
+            output: null,
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+
+        // always option
+        {
+            code: `
+                function baz(foo,
+                    bar
+                ) {}
+            `,
+            output: `
+                function baz(\nfoo,
+                    bar
+                ) {}
+            `,
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar) {})
+            `,
+            output: `
+                (function(
+                    foo,
+                    bar\n) {})
+            `,
+            options: ["always"],
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                (function baz(foo,
+                    bar) {})
+            `,
+            output: `
+                (function baz(\nfoo,
+                    bar\n) {})
+            `,
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: "function baz(foo, bar) {}",
+            output: "function baz(\nfoo, bar\n) {}",
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: "(function(foo, bar) {});",
+            output: "(function(\nfoo, bar\n) {});",
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: "(function baz(foo, bar) {});",
+            output: "(function baz(\nfoo, bar\n) {});",
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: "(foo, bar) => {};",
+            output: "(\nfoo, bar\n) => {};",
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: "baz(foo, bar);",
+            output: "baz(\nfoo, bar\n);",
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: "function baz() {}",
+            output: "function baz(\n) {}",
+            options: ["always"],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+
+        // never option
+        {
+            code: `
+                function baz(foo,
+                    bar
+                ) {}
+            `,
+            output: `
+                function baz(foo,
+                    bar) {}
+            `,
+            options: ["never"],
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar) {})
+            `,
+            output: `
+                (function(foo,
+                    bar) {})
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                    foo,
+                    bar
+                ) {}
+            `,
+            output: `
+                function baz(foo,
+                    bar) {}
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                (function(
+                    foo,
+                    bar
+                ) {});
+            `,
+            output: `
+                (function(foo,
+                    bar) {});
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                (function baz(
+                    foo,
+                    bar
+                ) {});
+            `,
+            output: `
+                (function baz(foo,
+                    bar) {});
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                (
+                    foo,
+                    bar
+                ) => {};
+            `,
+            output: `
+                (foo,
+                    bar) => {};
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                baz(
+                    foo,
+                    bar
+                );
+            `,
+            output: `
+                baz(foo,
+                    bar);
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                ) {}
+            `,
+            output: `
+                function baz() {}
+            `,
+            options: ["never"],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+
+        // minItems option
+        {
+            code: "function baz(foo, bar, qux) {}",
+            output: "function baz(\nfoo, bar, qux\n) {}",
+            options: [{ minItems: 3 }],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                function baz(
+                    foo, bar
+                ) {}
+            `,
+            output: `
+                function baz(foo, bar) {}
+            `,
+            options: [{ minItems: 3 }],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: "baz(foo, bar, qux);",
+            output: "baz(\nfoo, bar, qux\n);",
+            options: [{ minItems: 3 }],
+            errors: [LEFT_MISSING_ERROR, RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                baz(
+                    foo,
+                    bar
+                );
+            `,
+            output: `
+                baz(foo,
+                    bar);
+            `,
+            options: [{ minItems: 3 }],
+            errors: [LEFT_UNEXPECTED_ERROR, RIGHT_UNEXPECTED_ERROR]
+        },
+        {
+            code: `
+                foo(
+                    bar,
+                    baz)
+            `,
+            output: `
+                foo(
+                    bar,
+                    baz\n)
+            `,
+            options: ["consistent"],
+            errors: [RIGHT_MISSING_ERROR]
+        },
+        {
+            code: `
+                foo(bar,
+                    baz
+                )
+            `,
+            output: `
+                foo(bar,
+                    baz)
+            `,
+            options: ["consistent"],
+            errors: [RIGHT_UNEXPECTED_ERROR]
+        }
+    ]
+});
diff --git a/tests/lib/util/source-code.js b/tests/lib/util/source-code.js
index 642d62700..746d78568 100644
--- a/tests/lib/util/source-code.js
+++ b/tests/lib/util/source-code.js
@@ -205,7 +205,8 @@ describe("SourceCode", () => {
             it("should not has BOM in `text` property.", () => {
                 assert.equal(
                     sourceCode.text,
-                    "\"use strict\";\n\nconsole.log(\"This file has [0xEF, 0xBB, 0xBF] as BOM.\");\n");
+                    "\"use strict\";\n\nconsole.log(\"This file has [0xEF, 0xBB, 0xBF] as BOM.\");\n"
+                );
             });
         });
     });