Repository: eslint/eslint (js)

Issue:
"comma-style" doesn't like ignored, multiline destructured array elements.
**Tell us about your environment**

* **ESLint Version:** 6.8.0
* **Node Version:** 13.6.0
* **NPM Version:** 6.13.4
* **Parser** @typescript-eslint

**Please show your full configuration:**

<details>
<summary>Configuration</summary>
```js
module.exports = {
  env: {
    browser: true,
    node: true,
    es6: true,
  },
  extends: [
    'airbnb',
    'plugin:react/recommended',
    'plugin:import/typescript',
  ],
  globals: {
    PropTypes: false,
    React: false,
    uiVersion: false,
  },
  parser: '@typescript-eslint/parser',
  parserOptions: {
    ecmaFeatures: {
      jsx: true,
    },
    ecmaVersion: 2018,
    sourceType: 'module',
  },
  plugins: [
    '@typescript-eslint',
    'react',
    'react-hooks',
    'sort-destructure-keys',
  ],
  rules: {
    'array-bracket-newline': ['error', {
      'multiline': true,
      'minItems': 3,
    }],
    'array-element-newline': ['warn', {
      'multiline': true,
      'minItems': 3,
    }],
    'arrow-body-style': 'off',
    'arrow-parens': 'off',
    'camelcase': ['warn', {
      allow: ["^UNSAFE_", "doc_count", "^active_"],
    }],
    'comma-dangle': ['warn', 'always-multiline'],
    'consistent-return': ['warn', {
      treatUndefinedAsUnspecified: true
    }],
    'func-names': ['warn', 'as-needed'],
    'function-paren-newline': ['warn', 'consistent'],
    'implicit-arrow-linebreak': 'off',
    'indent': ['warn', 4, {
      ArrayExpression: 'first',
      CallExpression: { arguments: 'first' },
      flatTernaryExpressions: true,
      FunctionDeclaration: { parameters: 'first' },
      FunctionExpression: { parameters: 'first' },
      ignoreComments: true,
      ignoredNodes: [
        'ConditionalExpression',
        'JSXAttribute',
        'JSXClosingElement',
        'JSXElement',
        'JSXElement > *',
        'JSXEmptyExpression',
        'JSXExpressionContainer',
        'JSXIdentifier',
        'JSXMemberExpression',
        'JSXNamespacedName',
        'JSXOpeningElement',
        'JSXSpreadAttribute',
        'JSXSpreadChild',
        'JSXText',
        'TemplateLiteral > *',
      ],
      ImportDeclaration: 'first',
      MemberExpression: 1,
      ObjectExpression: 'first',
      SwitchCase: 1,
      VariableDeclarator: 'first',
    }],
    'multiline-ternary': ['warn', 'always-multiline'],
    'no-console': ['warn', {
      allow: [
        'info',
        'warn',
        'error',
      ]
    }],
    'no-debugger': 'warn',
    'no-fallthrough': ['warn', {
      commentPattern: 'break[\\s\\w]*omitted',
    }],
    'no-nested-ternary': 'off',
    'no-unneeded-ternary': 'warn',
    'no-unused-expressions': ['warn', {
      allowShortCircuit: true,
      allowTaggedTemplates: true,
      allowTernary: true,
    }],
    'no-var': 'error', // Must use const or let.
    'object-property-newline': ['warn', {
      // allowAllPropertiesOnSameLine: false,
    }],
    'object-curly-newline': 'warn', // ['warn', {
    // ObjectExpression: {
    //   'multiline': true,
    //   'minProperties': 2,
    // },
    // ObjectPattern: {
    //   'multiline': true,
    //   'minProperties': 2,
    // },
    // ImportDeclaration: {
    //   'multiline': true,
    //   'minProperties': 2,
    // },
    // ExportDeclaration: {
    //   'multiline': true,
    //   'minProperties': 2,
    // },
    // }],
    'operator-linebreak': ['warn', 'after', {
      overrides: {
        '?': 'before',
        ':': 'before',
      }
    }],
    'padded-blocks': 'error',
    'semi': ['warn', 'always'],
    'sort-keys': ['warn', 'asc', {
      caseSensitive: false,
      natural: true,
    }],
    'quotes': ['warn', 'single'],
    'import/extensions': 'off',
    'react/jsx-closing-bracket-location': ['warn', 'props-aligned'],
    'react/jsx-filename-extension': ['warn', {
      extensions: [
        '.js',
        '.jsx',
        '.tsx',
      ],
    }],
    'react/jsx-first-prop-new-line': ['warn', 'multiline'],
    'react/jsx-fragments': ['error', 'element'],
    'react/jsx-indent': ['warn', 2, {
      checkAttributes: true,
      indentLogicalExpressions: true,
    }],
    'react/jsx-indent-props': ['warn', 2],
    'react/jsx-max-props-per-line': ['warn', {
      maximum: 1,
      when: 'multiline',
    }],
    'react/jsx-one-expression-per-line': ['warn', {
      allow: 'single-child',
    }],
    'react/jsx-sort-default-props': 'error',
    'react/jsx-sort-props': ['warn', {
      ignoreCase: true,
    }],
    'react/jsx-tag-spacing': ['warn', {
      closingSlash: 'never',
      beforeSelfClosing: 'always',
      afterOpening: 'never',
      beforeClosing: 'never',
    }],
    'react/jsx-wrap-multilines': ['error', {
      declaration: 'parens-new-line',
      assignment: 'parens-new-line',
      return: 'parens-new-line',
      arrow: 'parens-new-line',
      condition: 'parens-new-line',
      logical: 'parens-new-line',
      prop: 'parens-new-line',
    }],
    'react/no-did-mount-set-state': 'warn',
    'react/no-did-update-set-state': 'warn',
    'react/no-direct-mutation-state': 'warn',
    'react/no-multi-comp': 'warn',
    'react/no-unknown-property': 'warn',
    'react/sort-comp': 'warn',
    'react/sort-prop-types': 'error',
    'react/prop-types': 'off',
    'sort-destructure-keys/sort-destructure-keys': ['warn', {
      caseSensitive: false,
    }],
    '@typescript-eslint/explicit-function-return-type': 'off', 
    '@typescript-eslint/explicit-member-accessibility': 'off',
  },
  settings: {
    'import/resolver': {
      node: {
        paths: ['./src/packages']
      },
    },
    react: {
      version: 'detect',
    },
  },
}
```
</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

Tried destructuring an array, while ignoring the first two elements (as per [MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Ignoring_some_returned_values)).
Expecting each element in a new line (my preference to ease code version management, discussion for another day), somehow the second comma is not happy unless the whole destructuring assignment is in a single line.

<!-- Paste the source code below: -->
```js
const [
    ,,
    configPath = 'config.json',
    authFilePath = 'freshbooks.auth',
] = process.argv;
```

```js
const [
    ,
    ,
    configPath = 'config.json',
    authFilePath = 'freshbooks.auth',
] = process.argv;
```

In both cases I get a `Bad line breaking before and after ','. (comma-style)` error message for the second comma.

Thought I'd use the "ArrayPattern" exception, but that would turn linting off for all array destructuring, and that's less desirable. Seems to me like the rule should understand ignored elements (bug), or we should have another exception/handling for these consecutive commas.
Running ESLint automatically, using `linter-eslint` on Atom.

Thoughts?

While looking for other issues like this, and found a couple closed ones:
- https://github.com/eslint/eslint/issues/1587 adds a fix,  yet doesn't address multiline code.
- https://github.com/eslint/eslint/issues/9968 closed without resolution, doesn't deal with multiline either.

**Are you willing to submit a pull request to fix this bug?**
Likely yes, if someone points me in the right direction (inside the repo).

Pull request:
Fix: ignore lines with empty elements (fixes #12756)
<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [X] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md).
- [X] The team has reached consensus on the changes proposed in this pull request. If not, I understand that the evaluation process will begin with this pull request and won't be merged until the team has reached consensus.

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[X] Bug fix
<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

#### What changes did you make? (Give an overview)
Fix the issue here: #12756

This PR fixes a bug in the rule **comma-style**, it ignores lines that are only made of empty elements. 
This is an example:

```
const [
    ,
    a,
    b,
] = arr;

```

####  Is there anything you'd like reviewers to focus on?
No.

Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index a7e0f1153..9bb665a3c 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -66,10 +66,13 @@ module.exports = {
     rules: {
         "eslint-plugin/consistent-output": "error",
         "eslint-plugin/no-deprecated-context-methods": "error",
+        "eslint-plugin/no-only-tests": "error",
         "eslint-plugin/prefer-output-null": "error",
         "eslint-plugin/prefer-placeholders": "error",
+        "eslint-plugin/prefer-replace-text": "error",
         "eslint-plugin/report-message-format": ["error", "[^a-z].*\\.$"],
         "eslint-plugin/require-meta-docs-description": "error",
+        "eslint-plugin/require-meta-schema": "error",
         "eslint-plugin/require-meta-type": "error",
         "eslint-plugin/test-case-property-ordering": "error",
         "eslint-plugin/test-case-shorthand-strings": "error",
@@ -80,6 +83,7 @@ module.exports = {
             files: ["lib/rules/*", "tools/internal-rules/*"],
             excludedFiles: ["index.js"],
             rules: {
+                "eslint-plugin/prefer-object-rule": "error",
                 "internal-rules/no-invalid-meta": "error",
                 "internal-rules/consistent-meta-messages": "error"
             }
diff --git a/Makefile.js b/Makefile.js
index 62b1d7666..6d1ee3919 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -197,7 +197,8 @@ function generateRuleIndexPage() {
                         name: basename,
                         description: rule.meta.docs.description,
                         recommended: rule.meta.docs.recommended || false,
-                        fixable: !!rule.meta.fixable
+                        fixable: !!rule.meta.fixable,
+                        hasSuggestions: !!rule.meta.docs.suggestion
                     },
                     category = categoriesData.categories.find(c => c.name === rule.meta.docs.category);
 
@@ -640,6 +641,7 @@ target.gensite = function(prereleaseVersion) {
 
     const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule.";
     const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.";
+    const HAS_SUGGESTIONS_TEXT = "\n\n(hasSuggestions) Some problems reported by this rule are manually fixable by editor [suggestions](../developer-guide/working-with-rules#providing-suggestions).";
 
     // 4. Loop through all files in temporary directory
     process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r");
@@ -669,13 +671,14 @@ target.gensite = function(prereleaseVersion) {
                 const rule = rules.get(ruleName);
                 const isRecommended = rule && rule.meta.docs.recommended;
                 const isFixable = rule && rule.meta.fixable;
+                const hasSuggestions = rule && rule.meta.docs.suggestion;
 
                 // Incorporate the special portion into the documentation content
                 const textSplit = text.split("\n");
                 const ruleHeading = textSplit[0];
                 const ruleDocsContent = textSplit.slice(1).join("\n");
 
-                text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}\n${ruleDocsContent}`;
+                text = `${ruleHeading}${isRecommended ? RECOMMENDED_TEXT : ""}${isFixable ? FIXABLE_TEXT : ""}${hasSuggestions ? HAS_SUGGESTIONS_TEXT : ""}\n${ruleDocsContent}`;
                 title = `${ruleName} - Rules`;
 
                 if (rule && rule.meta) {
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index ec53551c5..6aab5b566 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -355,7 +355,9 @@ The `LintResult` value is the information of the linting result of each file. Th
 * `fixableWarningCount` (`number`)<br>
   The number of warnings that can be fixed automatically by the `fix` constructor option.
 * `errorCount` (`number`)<br>
-  The number of errors. This includes fixable errors.
+  The number of errors. This includes fixable errors and fatal errors.
+* `fatalErrorCount` (`number`)<br>
+  The number of fatal errors.
 * `warningCount` (`number`)<br>
   The number of warnings. This includes fixable warnings.
 * `output` (`string | undefined`)<br>
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 99fd61eb9..4500e6ff6 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -81,6 +81,7 @@ Miscellaneous:
   --init                         Run config initialization wizard - default: false
   --env-info                     Output execution environment information - default: false
   --no-error-on-unmatched-pattern  Prevent errors when pattern is unmatched - default: false
+  --exit-on-fatal-error          Exit with exit code 2 in case of fatal error - default: false
   --debug                        Output debugging information
   -h, --help                     Show help
   -v, --version                  Output the version number
@@ -467,6 +468,10 @@ This option outputs information about the execution environment, including the v
 
 This option prevents errors when a quoted glob pattern or `--ext` is unmatched. This will not prevent errors when your shell can't match a glob.
 
+#### `--exit-on-fatal-error`
+
+This option causes ESLint to exit with exit code 2 if one or more fatal parsing errors occur. Without this option, fatal parsing errors are reported as rule violations.
+
 #### `--debug`
 
 This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs.
diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js
index ca298f9c3..24f6a10d1 100644
--- a/lib/cli-engine/cli-engine.js
+++ b/lib/cli-engine/cli-engine.js
@@ -156,6 +156,9 @@ function calculateStatsPerFile(messages) {
     return messages.reduce((stat, message) => {
         if (message.fatal || message.severity === 2) {
             stat.errorCount++;
+            if (message.fatal) {
+                stat.fatalErrorCount++;
+            }
             if (message.fix) {
                 stat.fixableErrorCount++;
             }
@@ -168,6 +171,7 @@ function calculateStatsPerFile(messages) {
         return stat;
     }, {
         errorCount: 0,
+        fatalErrorCount: 0,
         warningCount: 0,
         fixableErrorCount: 0,
         fixableWarningCount: 0
@@ -183,12 +187,14 @@ function calculateStatsPerFile(messages) {
 function calculateStatsPerRun(results) {
     return results.reduce((stat, result) => {
         stat.errorCount += result.errorCount;
+        stat.fatalErrorCount += result.fatalErrorCount;
         stat.warningCount += result.warningCount;
         stat.fixableErrorCount += result.fixableErrorCount;
         stat.fixableWarningCount += result.fixableWarningCount;
         return stat;
     }, {
         errorCount: 0,
+        fatalErrorCount: 0,
         warningCount: 0,
         fixableErrorCount: 0,
         fixableWarningCount: 0
diff --git a/lib/cli.js b/lib/cli.js
index f76676454..477310da5 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -131,14 +131,16 @@ function translateOptions({
  */
 function countErrors(results) {
     let errorCount = 0;
+    let fatalErrorCount = 0;
     let warningCount = 0;
 
     for (const result of results) {
         errorCount += result.errorCount;
+        fatalErrorCount += result.fatalErrorCount;
         warningCount += result.warningCount;
     }
 
-    return { errorCount, warningCount };
+    return { errorCount, fatalErrorCount, warningCount };
 }
 
 /**
@@ -314,9 +316,12 @@ const cli = {
         if (await printResults(engine, resultsToPrint, options.format, options.outputFile)) {
 
             // Errors and warnings from the original unfiltered results should determine the exit code
-            const { errorCount, warningCount } = countErrors(results);
+            const { errorCount, fatalErrorCount, warningCount } = countErrors(results);
+
             const tooManyWarnings =
                 options.maxWarnings >= 0 && warningCount > options.maxWarnings;
+            const shouldExitForFatalErrors =
+                options.exitOnFatalError && fatalErrorCount > 0;
 
             if (!errorCount && tooManyWarnings) {
                 log.error(
@@ -325,6 +330,10 @@ const cli = {
                 );
             }
 
+            if (shouldExitForFatalErrors) {
+                return 2;
+            }
+
             return (errorCount || tooManyWarnings) ? 1 : 0;
         }
 
diff --git a/lib/options.js b/lib/options.js
index 92c140bd3..e3661ec81 100644
--- a/lib/options.js
+++ b/lib/options.js
@@ -290,6 +290,12 @@ module.exports = optionator({
             default: "true",
             description: "Prevent errors when pattern is unmatched"
         },
+        {
+            option: "exit-on-fatal-error",
+            type: "Boolean",
+            default: "false",
+            description: "Exit with exit code 2 in case of fatal error"
+        },
         {
             option: "debug",
             type: "Boolean",
diff --git a/lib/rules/comma-style.js b/lib/rules/comma-style.js
index fbdecccaa..824ad89b2 100644
--- a/lib/rules/comma-style.js
+++ b/lib/rules/comma-style.js
@@ -216,6 +216,8 @@ module.exports = {
                         previousItemToken = tokenAfterItem
                             ? sourceCode.getTokenBefore(tokenAfterItem)
                             : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
+                    } else {
+                        previousItemToken = currentItemToken;
                     }
                 });
 
diff --git a/tools/internal-rules/no-invalid-meta.js b/tools/internal-rules/no-invalid-meta.js
index e5a74b359..69b43d4c8 100644
--- a/tools/internal-rules/no-invalid-meta.js
+++ b/tools/internal-rules/no-invalid-meta.js
@@ -50,17 +50,6 @@ function hasMetaDocs(metaPropertyNode) {
     return Boolean(getPropertyFromObject("docs", metaPropertyNode.value));
 }
 
-/**
- * Whether this `meta` ObjectExpression has a `docs.description` property defined or not.
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `docs.description` property exists.
- */
-function hasMetaDocsDescription(metaPropertyNode) {
-    const metaDocs = getPropertyFromObject("docs", metaPropertyNode.value);
-
-    return metaDocs && getPropertyFromObject("description", metaDocs.value);
-}
-
 /**
  * Whether this `meta` ObjectExpression has a `docs.category` property defined or not.
  * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
@@ -83,15 +72,6 @@ function hasMetaDocsRecommended(metaPropertyNode) {
     return metaDocs && getPropertyFromObject("recommended", metaDocs.value);
 }
 
-/**
- * Whether this `meta` ObjectExpression has a `schema` property defined or not.
- * @param {ASTNode} metaPropertyNode The `meta` ObjectExpression for this rule.
- * @returns {boolean} `true` if a `schema` property exists.
- */
-function hasMetaSchema(metaPropertyNode) {
-    return getPropertyFromObject("schema", metaPropertyNode.value);
-}
-
 /**
  * Checks the validity of the meta definition of this rule and reports any errors found.
  * @param {RuleContext} context The ESLint rule context.
@@ -111,11 +91,6 @@ function checkMetaValidity(context, exportsNode) {
         return;
     }
 
-    if (!hasMetaDocsDescription(metaProperty)) {
-        context.report({ node: metaProperty, messageId: "missingMetaDocsDescription" });
-        return;
-    }
-
     if (!hasMetaDocsCategory(metaProperty)) {
         context.report({ node: metaProperty, messageId: "missingMetaDocsCategory" });
         return;
@@ -123,23 +98,9 @@ function checkMetaValidity(context, exportsNode) {
 
     if (!hasMetaDocsRecommended(metaProperty)) {
         context.report({ node: metaProperty, messageId: "missingMetaDocsRecommended" });
-        return;
-    }
-
-    if (!hasMetaSchema(metaProperty)) {
-        context.report({ node: metaProperty, messageId: "missingMetaSchema" });
     }
 }
 
-/**
- * Whether this node is the correct format for a rule definition or not.
- * @param {ASTNode} node node that the rule exports.
- * @returns {boolean} `true` if the exported node is the correct format for a rule definition
- */
-function isCorrectExportsFormat(node) {
-    return node.type === "ObjectExpression";
-}
-
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -156,12 +117,9 @@ module.exports = {
         messages: {
             missingMeta: "Rule is missing a meta property.",
             missingMetaDocs: "Rule is missing a meta.docs property.",
-            missingMetaDocsDescription: "Rule is missing a meta.docs.description property.",
             missingMetaDocsCategory: "Rule is missing a meta.docs.category property.",
             missingMetaDocsRecommended: "Rule is missing a meta.docs.recommended property.",
-            missingMetaSchema: "Rule is missing a meta.schema property.",
-            noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
-            incorrectExport: "Rule does not export an Object. Make sure the rule follows the new rule format."
+            noExport: "Rule does not export anything. Make sure rule exports an object according to new rule format."
         }
     },
 
@@ -186,11 +144,6 @@ module.exports = {
                         node,
                         messageId: "noExport"
                     });
-                } else if (!isCorrectExportsFormat(exportsNode)) {
-                    context.report({
-                        node: exportsNode,
-                        messageId: "incorrectExport"
-                    });
                 } else {
                     checkMetaValidity(context, exportsNode);
                 }


Test patch:
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 7500d6611..4fed66aed 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -89,6 +89,7 @@ describe("bin/eslint.js", () => {
                     filePath: "<text>",
                     messages: [],
                     errorCount: 0,
+                    fatalErrorCount: 0,
                     warningCount: 0,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
@@ -117,6 +118,14 @@ describe("bin/eslint.js", () => {
             return assertExitCode(child, 1);
         });
 
+        it("has exit code 2 if a syntax error is thrown when exit-on-fatal-error is true", () => {
+            const child = runESLint(["--stdin", "--no-eslintrc", "--exit-on-fatal-error"]);
+
+            child.stdin.write("This is not valid JS syntax.\n");
+            child.stdin.end();
+            return assertExitCode(child, 2);
+        });
+
         it("has exit code 1 if a linting error occurs", () => {
             const child = runESLint(["--stdin", "--no-eslintrc", "--rule", "semi:2"]);
 
diff --git a/tests/fixtures/exit-on-fatal-error/fatal-error.js b/tests/fixtures/exit-on-fatal-error/fatal-error.js
new file mode 100644
index 000000000..53a47a5b7
--- /dev/null
+++ b/tests/fixtures/exit-on-fatal-error/fatal-error.js
@@ -0,0 +1,1 @@
+var foo  1
\ No newline at end of file
diff --git a/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js b/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js
new file mode 100644
index 000000000..a23ad4a79
--- /dev/null
+++ b/tests/fixtures/exit-on-fatal-error/no-fatal-error-rule-violation.js
@@ -0,0 +1,3 @@
+/*eslint no-unused-vars: "error"*/
+
+var foo = 1;
\ No newline at end of file
diff --git a/tests/fixtures/exit-on-fatal-error/no-fatal-error.js b/tests/fixtures/exit-on-fatal-error/no-fatal-error.js
new file mode 100644
index 000000000..d5814b856
--- /dev/null
+++ b/tests/fixtures/exit-on-fatal-error/no-fatal-error.js
@@ -0,0 +1,1 @@
+var foo = 1;
\ No newline at end of file
diff --git a/tests/lib/cli-engine/cli-engine.js b/tests/lib/cli-engine/cli-engine.js
index 59243b0b7..680317b29 100644
--- a/tests/lib/cli-engine/cli-engine.js
+++ b/tests/lib/cli-engine/cli-engine.js
@@ -144,6 +144,7 @@ describe("CLIEngine", () => {
             assert.strictEqual(report.results.length, 1);
             assert.strictEqual(report.errorCount, 5);
             assert.strictEqual(report.warningCount, 0);
+            assert.strictEqual(report.fatalErrorCount, 0);
             assert.strictEqual(report.fixableErrorCount, 3);
             assert.strictEqual(report.fixableWarningCount, 0);
             assert.strictEqual(report.results[0].messages.length, 5);
@@ -310,6 +311,7 @@ describe("CLIEngine", () => {
                         messages: [],
                         errorCount: 0,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "var bar = foo;"
@@ -317,6 +319,7 @@ describe("CLIEngine", () => {
                 ],
                 errorCount: 0,
                 warningCount: 0,
+                fatalErrorCount: 0,
                 fixableErrorCount: 0,
                 fixableWarningCount: 0,
                 usedDeprecatedRules: []
@@ -519,6 +522,7 @@ describe("CLIEngine", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         source: "var bar = foo"
@@ -526,6 +530,7 @@ describe("CLIEngine", () => {
                 ],
                 errorCount: 1,
                 warningCount: 0,
+                fatalErrorCount: 0,
                 fixableErrorCount: 0,
                 fixableWarningCount: 0,
                 usedDeprecatedRules: []
@@ -562,6 +567,7 @@ describe("CLIEngine", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 1,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "var bar = foothis is a syntax error."
@@ -569,6 +575,7 @@ describe("CLIEngine", () => {
                 ],
                 errorCount: 1,
                 warningCount: 0,
+                fatalErrorCount: 1,
                 fixableErrorCount: 0,
                 fixableWarningCount: 0,
                 usedDeprecatedRules: []
@@ -604,6 +611,7 @@ describe("CLIEngine", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 1,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         source: "var bar ="
@@ -611,6 +619,7 @@ describe("CLIEngine", () => {
                 ],
                 errorCount: 1,
                 warningCount: 0,
+                fatalErrorCount: 1,
                 fixableErrorCount: 0,
                 fixableWarningCount: 0,
                 usedDeprecatedRules: []
@@ -692,6 +701,7 @@ describe("CLIEngine", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 1,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         source: "var bar = foothis is a syntax error.\n return bar;"
@@ -699,6 +709,7 @@ describe("CLIEngine", () => {
                 ],
                 errorCount: 1,
                 warningCount: 0,
+                fatalErrorCount: 1,
                 fixableErrorCount: 0,
                 fixableWarningCount: 0,
                 usedDeprecatedRules: []
@@ -1684,6 +1695,7 @@ describe("CLIEngine", () => {
                         messages: [],
                         errorCount: 0,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "true ? \"yes\" : \"no\";\n"
@@ -1693,6 +1705,7 @@ describe("CLIEngine", () => {
                         messages: [],
                         errorCount: 0,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0
                     },
@@ -1713,6 +1726,7 @@ describe("CLIEngine", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n"
@@ -1734,6 +1748,7 @@ describe("CLIEngine", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "var msg = \"hi\" + foo;\n"
@@ -5077,6 +5092,7 @@ describe("CLIEngine", () => {
                             ],
                             errorCount: 1,
                             warningCount: 0,
+                            fatalErrorCount: 0,
                             fixableErrorCount: 0,
                             fixableWarningCount: 0,
                             source: "/* eslint-disable */"
@@ -5084,6 +5100,7 @@ describe("CLIEngine", () => {
                     ],
                     errorCount: 1,
                     warningCount: 0,
+                    fatalErrorCount: 0,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     usedDeprecatedRules: []
@@ -6155,7 +6172,8 @@ describe("CLIEngine", () => {
                             }
                         ],
                         source: "a == b",
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6177,7 +6195,8 @@ describe("CLIEngine", () => {
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6223,7 +6242,8 @@ describe("CLIEngine", () => {
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6258,7 +6278,8 @@ describe("CLIEngine", () => {
                             }
                         ],
                         source: "a == b",
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index d1fea182d..dbfe03c26 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -803,6 +803,38 @@ describe("cli", () => {
         });
     });
 
+    describe("when given the exit-on-fatal-error flag", () => {
+        it("should not change exit code if no fatal errors are reported", async () => {
+            const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error.js");
+            const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+            assert.strictEqual(exitCode, 0);
+        });
+
+        it("should exit with exit code 1 if no fatal errors are found, but rule violations are found", async () => {
+            const filePath = getFixturePath("exit-on-fatal-error", "no-fatal-error-rule-violation.js");
+            const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+            assert.strictEqual(exitCode, 1);
+        });
+
+        it("should exit with exit code 2 if fatal error is found", async () => {
+            const filePath = getFixturePath("exit-on-fatal-error", "fatal-error.js");
+            const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+            assert.strictEqual(exitCode, 2);
+        });
+
+        it("should exit with exit code 2 if fatal error is found in any file", async () => {
+            const filePath = getFixturePath("exit-on-fatal-error");
+            const exitCode = await cli.execute(`--no-ignore --exit-on-fatal-error ${filePath}`);
+
+            assert.strictEqual(exitCode, 2);
+        });
+
+
+    });
+
     describe("when passed --no-inline-config", () => {
         let localCLI;
 
diff --git a/tests/lib/eslint/eslint.js b/tests/lib/eslint/eslint.js
index c5a1b7360..aa35b842e 100644
--- a/tests/lib/eslint/eslint.js
+++ b/tests/lib/eslint/eslint.js
@@ -397,6 +397,7 @@ describe("ESLint", () => {
                     messages: [],
                     errorCount: 0,
                     warningCount: 0,
+                    fatalErrorCount: 0,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     output: "var bar = foo;",
@@ -597,6 +598,7 @@ describe("ESLint", () => {
                     ],
                     errorCount: 1,
                     warningCount: 0,
+                    fatalErrorCount: 0,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     source: "var bar = foo",
@@ -636,6 +638,7 @@ describe("ESLint", () => {
                     ],
                     errorCount: 1,
                     warningCount: 0,
+                    fatalErrorCount: 1,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     output: "var bar = foothis is a syntax error.",
@@ -674,6 +677,7 @@ describe("ESLint", () => {
                     ],
                     errorCount: 1,
                     warningCount: 0,
+                    fatalErrorCount: 1,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     source: "var bar =",
@@ -761,6 +765,7 @@ describe("ESLint", () => {
                     ],
                     errorCount: 1,
                     warningCount: 0,
+                    fatalErrorCount: 1,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     source: "var bar = foothis is a syntax error.\n return bar;",
@@ -1658,6 +1663,7 @@ describe("ESLint", () => {
                         messages: [],
                         errorCount: 0,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "true ? \"yes\" : \"no\";\n",
@@ -1668,6 +1674,7 @@ describe("ESLint", () => {
                         messages: [],
                         errorCount: 0,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         usedDeprecatedRules: []
@@ -1689,6 +1696,7 @@ describe("ESLint", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "var msg = \"hi\";\nif (msg == \"hi\") {\n\n}\n",
@@ -1711,6 +1719,7 @@ describe("ESLint", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         output: "var msg = \"hi\" + foo;\n",
@@ -5006,6 +5015,7 @@ describe("ESLint", () => {
                         ],
                         errorCount: 1,
                         warningCount: 0,
+                        fatalErrorCount: 0,
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         source: "/* eslint-disable */",
@@ -6060,7 +6070,8 @@ describe("ESLint", () => {
                         ],
                         source: "a == b",
                         usedDeprecatedRules: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6083,7 +6094,8 @@ describe("ESLint", () => {
                         fixableWarningCount: 0,
                         messages: [],
                         usedDeprecatedRules: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6130,7 +6142,8 @@ describe("ESLint", () => {
                         fixableWarningCount: 0,
                         messages: [],
                         usedDeprecatedRules: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6166,7 +6179,8 @@ describe("ESLint", () => {
                         ],
                         source: "a == b",
                         usedDeprecatedRules: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
diff --git a/tests/lib/rules/comma-style.js b/tests/lib/rules/comma-style.js
index 1eb308254..e0ac78d68 100644
--- a/tests/lib/rules/comma-style.js
+++ b/tests/lib/rules/comma-style.js
@@ -233,7 +233,45 @@ ruleTester.run("comma-style", rule, {
                     NewExpression: true
                 }
             }]
+        },
+        "var foo = [\n , \n 1, \n 2 \n];",
+        {
+            code: "const [\n , \n , \n a, \n b, \n] = arr;",
+            options: ["last", {
+                exceptions: {
+                    ArrayPattern: false
+                }
+            }],
+            parserOptions: {
+                ecmaVersion: 6
+            }
+        },
+        {
+            code: "const [\n ,, \n a, \n b, \n] = arr;",
+            options: ["last", {
+                exceptions: {
+                    ArrayPattern: false
+                }
+            }],
+            parserOptions: {
+                ecmaVersion: 6
+            }
+        },
+        {
+            code: "const arr = [\n 1 \n , \n ,2 \n]",
+            options: ["first"],
+            parserOptions: {
+                ecmaVersion: 6
+            }
+        },
+        {
+            code: "const arr = [\n ,'fifi' \n]",
+            options: ["first"],
+            parserOptions: {
+                ecmaVersion: 6
+            }
         }
+
     ],
 
     invalid: [
diff --git a/tests/tools/internal-rules/no-invalid-meta.js b/tests/tools/internal-rules/no-invalid-meta.js
index a5ff4d55a..6db173a15 100644
--- a/tests/tools/internal-rules/no-invalid-meta.js
+++ b/tests/tools/internal-rules/no-invalid-meta.js
@@ -96,20 +96,6 @@ ruleTester.run("no-invalid-meta", rule, {
         ].join("\n")
     ],
     invalid: [
-        {
-            code: [
-                "module.exports = function(context) {",
-                "    return {",
-                "        Program: function(node) {}",
-                "    };",
-                "};"
-            ].join("\n"),
-            errors: [{
-                messageId: "incorrectExport",
-                line: 1,
-                column: 18
-            }]
-        },
         {
             code: [
                 "module.exports = {",
@@ -164,30 +150,6 @@ ruleTester.run("no-invalid-meta", rule, {
                 column: 5
             }]
         },
-        {
-            code: [
-                "module.exports = {",
-                "    meta: {",
-                "        docs: {",
-                "            category: 'Internal',",
-                "            recommended: false",
-                "        },",
-                "        schema: []",
-                "    },",
-
-                "    create: function(context) {",
-                "        return {",
-                "            Program: function(node) {}",
-                "        };",
-                "    }",
-                "};"
-            ].join("\n"),
-            errors: [{
-                messageId: "missingMetaDocsDescription",
-                line: 2,
-                column: 5
-            }]
-        },
         {
             code: [
                 "module.exports = {",
@@ -236,30 +198,6 @@ ruleTester.run("no-invalid-meta", rule, {
                 column: 5
             }]
         },
-        {
-            code: [
-                "module.exports = {",
-                "    meta: {",
-                "        docs: {",
-                "            description: 'some rule',",
-                "            category: 'Internal',",
-                "            recommended: false",
-                "        }",
-                "    },",
-
-                "    create: function(context) {",
-                "        return {",
-                "            Program: function(node) {}",
-                "        };",
-                "    }",
-                "};"
-            ].join("\n"),
-            errors: [{
-                messageId: "missingMetaSchema",
-                line: 2,
-                column: 5
-            }]
-        },
         {
             code: "",
             errors: [{