Repository: eslint/eslint
Pull request #10569 (2018-07-06 14:00:41)

Docs: Clarifies option defaults in max-lines-per-function docs
**What is the purpose of this pull request? (put an "X" next to item)**

[x] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

**What changes did you make? (Give an overview)**

I made a change to the documentation for the `max-lines-per-function` rule to clarify the default values for the `skipComments`, `skipBlankLines` and `IIFEs` options. We originally read the `true` as a default.

I confirmed that the defaults for the three Boolean options are `false` from:

**[/lib/rules/max-lines-per-function.js:90](https://github.com/eslint/eslint/blob/0feedfdb267e110775f0a50dd5bc801c77260ae5/lib/rules/max-lines-per-function.js#L90)**
```js
let skipComments = false;
let skipBlankLines = false;
let IIFEs = false;
```
I did take a look at the documentation for about 5 other rules to see if there seemed to be a pattern for indicating defaults, but there was not a lot of consistency, so I just followed the style already established in this rule's documentation.

**Is there anything you'd like reviewers to focus on?**

If there's a desired composition pattern for this, please let me know! Thanks.


not-an-aardvark: That would be great!
Aladdin-ADD: sure, any docs improvement are welcome! (follows the template: https://github.com/eslint/generator-eslint/blob/master/rule/templates/_doc.md)

Patch:
diff --git a/README.md b/README.md
index 34fd0ef36..572cd8a07 100644
--- a/README.md
+++ b/README.md
@@ -157,7 +157,7 @@ Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](ht
 
 ## Releases
 
-We have scheduled releases every two weeks on Friday or Saturday.
+We have scheduled releases every two weeks on Friday or Saturday. You can follow a [release issue](https://github.com/eslint/eslint/issues?q=is%3Aopen+is%3Aissue+label%3Arelease) for updates about the scheduling of any particular release.
 
 ## Semantic Versioning Policy
 
diff --git a/docs/rules/max-lines-per-function.md b/docs/rules/max-lines-per-function.md
index 8ce0e313e..22ffea79f 100644
--- a/docs/rules/max-lines-per-function.md
+++ b/docs/rules/max-lines-per-function.md
@@ -40,11 +40,11 @@ This rule has the following options that can be specified using an object:
 
 * `"max"` (default `50`) enforces a maximum number of lines in a function.
 
-* `"skipBlankLines": true` ignore lines made up purely of whitespace.
+* `"skipBlankLines"` (default `false`) ignore lines made up purely of whitespace.
 
-* `"skipComments": true` ignore lines containing just comments.
+* `"skipComments"` (default `false`) ignore lines containing just comments.
 
-* `"IIFEs": true` include any code included in IIFEs.
+* `"IIFEs"` (default `false`) include any code included in IIFEs.
 
 Alternatively, you may specify a single integer for the `max` option:
 
diff --git a/docs/rules/sort-keys.md b/docs/rules/sort-keys.md
index 43d3dc522..d3f135a9e 100644
--- a/docs/rules/sort-keys.md
+++ b/docs/rules/sort-keys.md
@@ -52,6 +52,9 @@ let obj = {a: 1, [c + d]: 3, b: 2};
 let obj = {a: 1, ["c" + "d"]: 3, b: 2};
 let obj = {a: 1, [`${c}`]: 3, b: 2};
 let obj = {a: 1, [tag`c`]: 3, b: 2};
+
+// This rule ignores objects that have a spread operator in them.
+let obj = {b: 1, ...c, a: 2};
 ```
 
 ## Options
diff --git a/lib/code-path-analysis/code-path-analyzer.js b/lib/code-path-analysis/code-path-analyzer.js
index 80b73faa3..f4a7fee7f 100644
--- a/lib/code-path-analysis/code-path-analyzer.js
+++ b/lib/code-path-analysis/code-path-analyzer.js
@@ -60,7 +60,7 @@ function isForkingByTrueOrFalse(node) {
             return parent.test === node;
 
         case "LogicalExpression":
-            return true;
+            return isHandledLogicalOperator(parent.operator);
 
         default:
             return false;
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 185d4650d..6b151f46a 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -23,10 +23,15 @@ const ruleValidators = new WeakMap();
 let validateSchema;
 
 // Defitions for deprecation warnings.
-const deprecationWarningMessages = Object.freeze({
+const deprecationWarningMessages = {
     ESLINT_LEGACY_ECMAFEATURES: "The 'ecmaFeatures' config file property is deprecated, and has no effect.",
     ESLINT_LEGACY_OBJECT_REST_SPREAD: "The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead."
-});
+};
+const severityMap = {
+    error: 2,
+    warn: 1,
+    off: 0
+};
 
 /**
  * Gets a complete options schema for a rule.
@@ -65,12 +70,14 @@ function getRuleOptionsSchema(rule) {
  */
 function validateRuleSeverity(options) {
     const severity = Array.isArray(options) ? options[0] : options;
+    const normSeverity = typeof severity === "string" ? severityMap[severity.toLowerCase()] : severity;
 
-    if (severity !== 0 && severity !== 1 && severity !== 2 && !(typeof severity === "string" && /^(?:off|warn|error)$/i.test(severity))) {
-        throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/g, "\"").replace(/\n/g, "")}').\n`);
+    if (normSeverity === 0 || normSeverity === 1 || normSeverity === 2) {
+        return normSeverity;
     }
 
-    return severity;
+    throw new Error(`\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '${util.inspect(severity).replace(/'/g, "\"").replace(/\n/g, "")}').\n`);
+
 }
 
 /**
@@ -116,7 +123,7 @@ function validateRuleOptions(rule, ruleId, options, source) {
     try {
         const severity = validateRuleSeverity(options);
 
-        if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) {
+        if (severity !== 0) {
             validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
         }
     } catch (err) {
diff --git a/lib/formatters/stylish.js b/lib/formatters/stylish.js
index e586fe857..48d70ecb3 100644
--- a/lib/formatters/stylish.js
+++ b/lib/formatters/stylish.js
@@ -89,7 +89,7 @@ module.exports = function(results) {
 
         if (fixableErrorCount > 0 || fixableWarningCount > 0) {
             output += chalk[summaryColor].bold([
-                "  ", fixableErrorCount, pluralize(" error", fixableErrorCount), ", ",
+                "  ", fixableErrorCount, pluralize(" error", fixableErrorCount), " and ",
                 fixableWarningCount, pluralize(" warning", fixableWarningCount),
                 " potentially fixable with the `--fix` option.\n"
             ].join(""));
diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js
index a7c46b34d..a784e2e31 100644
--- a/lib/rules/prefer-const.js
+++ b/lib/rules/prefer-const.js
@@ -170,7 +170,7 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
                     const elements = leftNode.elements;
 
                     hasOuterVariables = elements
-                        .map(element => element.name)
+                        .map(element => element && element.name)
                         .some(name => isOuterVariableInDestructing(name, variable.scope));
                 }
                 if (hasOuterVariables) {
diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js
index 9318e2fbb..85bfe56d5 100644
--- a/lib/rules/sort-keys.js
+++ b/lib/rules/sort-keys.js
@@ -125,7 +125,7 @@ module.exports = {
             },
 
             Property(node) {
-                if (node.parent.type === "ObjectPattern") {
+                if (node.parent.type === "ObjectPattern" || node.parent.properties.some(n => n.type === "SpreadElement")) {
                     return;
                 }
 


Test patch:
diff --git a/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js b/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js
new file mode 100644
index 000000000..248480710
--- /dev/null
+++ b/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js
@@ -0,0 +1,227 @@
+/**
+ * The output AST has been modified to represent an unknown operator
+ * (replaced ?? with %%)
+ *
+ * Parser: babel-eslint v8.2.3
+ * (with this fix for tokens: https://github.com/babel/babel-eslint/pull/632)
+ * Source code:
+ * foo && bar ?? baz
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    start: 0,
+    end: 17,
+    loc: {
+        start: {
+            line: 1,
+            column: 0
+        },
+        end: {
+            line: 1,
+            column: 17
+        }
+    },
+    range: [0, 17],
+    comments: [],
+    tokens: [
+        {
+            type: "Identifier",
+            value: "foo",
+            start: 0,
+            end: 3,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 0
+                },
+                end: {
+                    line: 1,
+                    column: 3
+                }
+            },
+            range: [0, 3]
+        },
+        {
+            type: "Punctuator",
+            value: "&&",
+            start: 4,
+            end: 6,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 4
+                },
+                end: {
+                    line: 1,
+                    column: 6
+                }
+            },
+            range: [4, 6]
+        },
+        {
+            type: "Identifier",
+            value: "bar",
+            start: 7,
+            end: 10,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 7
+                },
+                end: {
+                    line: 1,
+                    column: 10
+                }
+            },
+            range: [7, 10]
+        },
+        {
+            type: "Punctuator",
+            value: "%%",
+            start: 11,
+            end: 13,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 11
+                },
+                end: {
+                    line: 1,
+                    column: 13
+                }
+            },
+            range: [11, 13]
+        },
+        {
+            type: "Identifier",
+            value: "baz",
+            start: 14,
+            end: 17,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 14
+                },
+                end: {
+                    line: 1,
+                    column: 17
+                }
+            },
+            range: [14, 17]
+        }
+    ],
+    sourceType: "module",
+    body: [
+        {
+            type: "ExpressionStatement",
+            start: 0,
+            end: 17,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 0
+                },
+                end: {
+                    line: 1,
+                    column: 17
+                }
+            },
+            range: [0, 17],
+            expression: {
+                type: "LogicalExpression",
+                start: 0,
+                end: 17,
+                loc: {
+                    start: {
+                        line: 1,
+                        column: 0
+                    },
+                    end: {
+                        line: 1,
+                        column: 17
+                    }
+                },
+                range: [0, 17],
+                left: {
+                    type: "LogicalExpression",
+                    start: 0,
+                    end: 10,
+                    loc: {
+                        start: {
+                            line: 1,
+                            column: 0
+                        },
+                        end: {
+                            line: 1,
+                            column: 10
+                        }
+                    },
+                    range: [0, 10],
+                    left: {
+                        type: "Identifier",
+                        start: 0,
+                        end: 3,
+                        loc: {
+                            start: {
+                                line: 1,
+                                column: 0
+                            },
+                            end: {
+                                line: 1,
+                                column: 3
+                            },
+                            identifierName: "foo"
+                        },
+                        range: [0, 3],
+                        name: "foo",
+                        _babelType: "Identifier"
+                    },
+                    operator: "&&",
+                    right: {
+                        type: "Identifier",
+                        start: 7,
+                        end: 10,
+                        loc: {
+                            start: {
+                                line: 1,
+                                column: 7
+                            },
+                            end: {
+                                line: 1,
+                                column: 10
+                            },
+                            identifierName: "bar"
+                        },
+                        range: [7, 10],
+                        name: "bar",
+                        _babelType: "Identifier"
+                    },
+                    _babelType: "LogicalExpression"
+                },
+                operator: "%%",
+                right: {
+                    type: "Identifier",
+                    start: 14,
+                    end: 17,
+                    loc: {
+                        start: {
+                            line: 1,
+                            column: 14
+                        },
+                        end: {
+                            line: 1,
+                            column: 17
+                        },
+                        identifierName: "baz"
+                    },
+                    range: [14, 17],
+                    name: "baz",
+                    _babelType: "Identifier"
+                },
+                _babelType: "LogicalExpression"
+            },
+            _babelType: "ExpressionStatement"
+        }
+    ]
+});
diff --git a/tests/lib/formatters/stylish.js b/tests/lib/formatters/stylish.js
index 813788016..2a88a6dae 100644
--- a/tests/lib/formatters/stylish.js
+++ b/tests/lib/formatters/stylish.js
@@ -107,7 +107,7 @@ describe("formatter:stylish", () => {
             it("should return a string in the correct format", () => {
                 const result = formatter(code);
 
-                assert.strictEqual(result, "\nfoo.js\n  5:10  error  Unexpected foo  foo\n\n\u2716 1 problem (1 error, 0 warnings)\n  1 error, 0 warnings potentially fixable with the `--fix` option.\n");
+                assert.strictEqual(result, "\nfoo.js\n  5:10  error  Unexpected foo  foo\n\n\u2716 1 problem (1 error, 0 warnings)\n  1 error and 0 warnings potentially fixable with the `--fix` option.\n");
                 assert.strictEqual(chalkStub.yellow.bold.callCount, 0);
                 assert.strictEqual(chalkStub.red.bold.callCount, 2);
             });
@@ -146,7 +146,7 @@ describe("formatter:stylish", () => {
             it("should return a string in the correct format", () => {
                 const result = formatter(code);
 
-                assert.strictEqual(result, "\nfoo.js\n  5:10  warning  Unexpected foo  foo\n\n\u2716 1 problem (0 errors, 1 warning)\n  0 errors, 1 warning potentially fixable with the `--fix` option.\n");
+                assert.strictEqual(result, "\nfoo.js\n  5:10  warning  Unexpected foo  foo\n\n\u2716 1 problem (0 errors, 1 warning)\n  0 errors and 1 warning potentially fixable with the `--fix` option.\n");
                 assert.strictEqual(chalkStub.yellow.bold.callCount, 2);
                 assert.strictEqual(chalkStub.red.bold.callCount, 0);
             });
@@ -351,7 +351,7 @@ describe("formatter:stylish", () => {
 
             const result = formatter(code);
 
-            assert.include(result, "  1 error, 0 warnings potentially fixable with the `--fix` option.\n");
+            assert.include(result, "  1 error and 0 warnings potentially fixable with the `--fix` option.\n");
         });
 
         it("should output fixable problems message when warnings are fixable", () => {
@@ -368,7 +368,7 @@ describe("formatter:stylish", () => {
 
             const result = formatter(code);
 
-            assert.include(result, "  0 errors, 2 warnings potentially fixable with the `--fix` option.\n");
+            assert.include(result, "  0 errors and 2 warnings potentially fixable with the `--fix` option.\n");
         });
 
         it("should output the total number of fixable errors and warnings", () => {
@@ -394,7 +394,7 @@ describe("formatter:stylish", () => {
 
             const result = formatter(code);
 
-            assert.include(result, "  9 errors, 3 warnings potentially fixable with the `--fix` option.\n");
+            assert.include(result, "  9 errors and 3 warnings potentially fixable with the `--fix` option.\n");
         });
     });
 });
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 251057021..e06f3cd9a 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -4480,6 +4480,16 @@ describe("Linter", () => {
                 assert.strictEqual(messages.length, 0);
             });
 
+            it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
+                const code = "foo && bar %% baz";
+                const parser = path.join(parserFixtures, "unknown-operators", "unknown-logical-operator-nested.js");
+
+                // This shouldn't throw
+                const messages = linter.verify(code, { parser }, filename, true);
+
+                assert.strictEqual(messages.length, 0);
+            });
+
             it("should strip leading line: prefix from parser error", () => {
                 const parser = path.join(parserFixtures, "line-error.js");
                 const messages = linter.verify(";", { parser }, "filename");
diff --git a/tests/lib/rules/prefer-const.js b/tests/lib/rules/prefer-const.js
index 59865ec86..c7b8a93c2 100644
--- a/tests/lib/rules/prefer-const.js
+++ b/tests/lib/rules/prefer-const.js
@@ -121,7 +121,11 @@ ruleTester.run("prefer-const", rule, {
         {
             code: "let x; function foo() { bar(x); } x = 0;",
             options: [{ ignoreReadBeforeAssign: true }]
-        }
+        },
+
+        // https://github.com/eslint/eslint/issues/10520
+        "const x = [1,2]; let y; [,y] = x; y = 0;",
+        "const x = [1,2,3]; let y, z; [y,,z] = x; y = 0; z = 0;"
     ],
     invalid: [
         {
@@ -361,6 +365,21 @@ ruleTester.run("prefer-const", rule, {
                 { message: "'foo' is never reassigned. Use 'const' instead.", type: "Identifier" },
                 { message: "'bar' is never reassigned. Use 'const' instead.", type: "Identifier" }
             ]
+        },
+
+        // https://github.com/eslint/eslint/issues/10520
+        {
+            code: "const x = [1,2]; let [,y] = x;",
+            output: "const x = [1,2]; const [,y] = x;",
+            errors: [{ message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" }]
+        },
+        {
+            code: "const x = [1,2,3]; let [y,,z] = x;",
+            output: "const x = [1,2,3]; const [y,,z] = x;",
+            errors: [
+                { message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" },
+                { message: "'z' is never reassigned. Use 'const' instead.", type: "Identifier" }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js
index c7cf8ecf4..5a3a0a057 100644
--- a/tests/lib/rules/sort-keys.js
+++ b/tests/lib/rules/sort-keys.js
@@ -35,6 +35,7 @@ ruleTester.run("sort-keys", rule, {
 
         // ignore spread properties.
         { code: "var obj = {a:1, ...z, b:1}", options: [], parserOptions: { ecmaVersion: 2018 } },
+        { code: "var obj = {b:1, ...z, a:1}", options: [], parserOptions: { ecmaVersion: 2018 } },
 
         // ignore destructuring patterns.
         { code: "let {a, b} = {}", options: [], parserOptions: { ecmaVersion: 6 } },
@@ -157,17 +158,6 @@ ruleTester.run("sort-keys", rule, {
             errors: ["Expected object keys to be in ascending order. 'a' should be before 'b'."]
         },
 
-        // ignore spred properties.
-        {
-            code: "var obj = {b:1, ...z, a:1}",
-            parserOptions: {
-                ecmaVersion: 2018
-            },
-            errors: [
-                "Expected object keys to be in ascending order. 'a' should be before 'b'."
-            ]
-        },
-
         // nested
         {
             code: "var obj = {a:1, c:{y:1, x:1}, b:1}",