Repository: eslint/eslint
Pull request #14316 (2021-04-12 03:51:05)

Breaking: Drop `codeframe` and `table` formatters
Fixes https://github.com/eslint/eslint/issues/14277
Related to https://github.com/eslint/eslint/issues/14098

<!--
    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).

#### What is the purpose of this pull request? (put an "X" next to an item)

[ ] 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
[x] Other, please explain:

Removal of features. Breaking

<!--
    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)

I dropped the `table` and `codeframe` formatters from the core as discussed in https://github.com/eslint/eslint/issues/14277

#### Is there anything you'd like reviewers to focus on?


bl-ue: ping @fregante — any progress?  Would be cool to get this merged 😎
fregante: Sorry for the delay!
TimvdLippe: I see that the 8.0.0 milestone has been created, excited for that! Can we add this PR to the milestone as well? Thanks!
nzakas: Only issues are in v8.0.0 board. This PR is there by virtue of the linked issue. 
TimvdLippe: Ah of course. Sorry for the noise!

Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index 9bb665a3c..b405b82fc 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -67,6 +67,7 @@ module.exports = {
         "eslint-plugin/consistent-output": "error",
         "eslint-plugin/no-deprecated-context-methods": "error",
         "eslint-plugin/no-only-tests": "error",
+        "eslint-plugin/prefer-message-ids": "error",
         "eslint-plugin/prefer-output-null": "error",
         "eslint-plugin/prefer-placeholders": "error",
         "eslint-plugin/prefer-replace-text": "error",
@@ -84,8 +85,7 @@ module.exports = {
             excludedFiles: ["index.js"],
             rules: {
                 "eslint-plugin/prefer-object-rule": "error",
-                "internal-rules/no-invalid-meta": "error",
-                "internal-rules/consistent-meta-messages": "error"
+                "internal-rules/no-invalid-meta": "error"
             }
         },
         {
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b1972878e..fc3d1e701 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -26,7 +26,7 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest]
-        node: [16.x, 15.x, 14.x, 13.x, 12.x, 10.x, "10.12.0"]
+        node: [16.x, 14.x, 12.x, "12.22.0"]
         include:
         - os: windows-latest
           node: "12.x"
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..127c24dc7
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+  push:
+    branches: [ master ]
+  pull_request:
+    # The branches below must be a subset of the branches above
+    branches: [ master ]
+  schedule:
+    - cron: '28 17 * * 5'
+
+jobs:
+  analyze:
+    name: Analyze
+    runs-on: ubuntu-latest
+    permissions:
+      actions: read
+      contents: read
+      security-events: write
+
+    strategy:
+      fail-fast: false
+      matrix:
+        language: [ 'javascript' ]
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+        # Learn more:
+        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+    steps:
+    - name: Checkout repository
+      uses: actions/checkout@v2
+
+    # Initializes the CodeQL tools for scanning.
+    - name: Initialize CodeQL
+      uses: github/codeql-action/init@v1
+      with:
+        languages: ${{ matrix.language }}
+        # If you wish to specify custom queries, you can do so here or in a config file.
+        # By default, queries listed here will override any specified in a config file.
+        # Prefix the list here with "+" to use these queries and those in the config file.
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
+    # If this step fails, then you should remove it and run the build manually (see below)
+    - name: Autobuild
+      uses: github/codeql-action/autobuild@v1
+
+    # ℹ️ Command-line programs to run using the OS shell.
+    # 📚 https://git.io/JvXDl
+
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+    #    and modify them (or add more) to build your code if your project
+    #    uses a compiled language
+
+    #- run: |
+    #   make bootstrap
+    #   make release
+
+    - name: Perform CodeQL Analysis
+      uses: github/codeql-action/analyze@v1
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd7fb6b5e..a63180815 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,19 @@
+v7.32.0 - July 30, 2021
+
+* [`3c78a7b`](https://github.com/eslint/eslint/commit/3c78a7bff6044fd196ae3b737983e6744c6eb7c8) Chore: Adopt `eslint-plugin/prefer-message-ids` rule internally (#14841) (Bryan Mishkin)
+* [`faecf56`](https://github.com/eslint/eslint/commit/faecf56cdb4146b28bfa4f1980adb41b4d3614b1) Update: change reporting location for `curly` rule (refs #12334) (#14766) (Nitin Kumar)
+* [`d7dc07a`](https://github.com/eslint/eslint/commit/d7dc07a15e256cee9232183165e2f6102f2c0873) Fix: ignore lines with empty elements (fixes #12756) (#14837) (Soufiane Boutahlil)
+* [`1bfbefd`](https://github.com/eslint/eslint/commit/1bfbefdaaf19ef32df42b89a3f5d32cff1e5b831) New: Exit on fatal error (fixes #13711) (#14730) (Antonios Katopodis)
+* [`ed007c8`](https://github.com/eslint/eslint/commit/ed007c82ee9d2170c87500d98303554b5f90b915) Chore: Simplify internal `no-invalid-meta` rule (#14842) (Bryan Mishkin)
+* [`d53d906`](https://github.com/eslint/eslint/commit/d53d9064b9dd0dd6a8ea39e07b16310c8364db69) Docs: Prepare data for website to indicate rules with suggestions (#14830) (Bryan Mishkin)
+* [`d28f2ff`](https://github.com/eslint/eslint/commit/d28f2ffb986e49d6da5c1d91215580591f4cfd35) Docs: Reference eslint-config-eslint to avoid potential for staleness (#14805) (Brett Zamir)
+* [`8be8a36`](https://github.com/eslint/eslint/commit/8be8a36010145dfcd31cbdd4f781a91989e3b1bd) Chore: Adopt `eslint-plugin/require-meta-docs-url` rule internally (#14823) (Bryan Mishkin)
+* [`f9c164f`](https://github.com/eslint/eslint/commit/f9c164f7b74ca73384c8c80eed5bdbe359b44f6c) Docs: New syntax issue template (#14826) (Nicholas C. Zakas)
+* [`eba0c45`](https://github.com/eslint/eslint/commit/eba0c4595c126a91f700d5f2e8723ec3f820a830) Chore: assertions on reporting loc in `unicode-bom` (refs #12334) (#14809) (Nitin Kumar)
+* [`ed945bd`](https://github.com/eslint/eslint/commit/ed945bd662714b1917e9de71d5b322a28be9161b) Docs: fix multiple broken links (#14833) (Sam Chen)
+* [`60df44c`](https://github.com/eslint/eslint/commit/60df44c79b0f74406119c0c040a360ca84e721fc) Chore: use `actions/setup-node@v2` (#14816) (Nitin Kumar)
+* [`6641d88`](https://github.com/eslint/eslint/commit/6641d88e17d952a8e51df5e0d3882a842d4c3f35) Docs: Update README team and sponsors (ESLint Jenkins)
+
 v7.31.0 - July 17, 2021
 
 * [`efdbb12`](https://github.com/eslint/eslint/commit/efdbb1227019427ec2d968a8d6e9151dd8a77c35) Upgrade: @eslint/eslintrc to v0.4.3 (#14808) (Brandon Mills)
diff --git a/LICENSE b/LICENSE
index 7fe552a86..b607bb36e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright JS Foundation and other contributors, https://js.foundation
+Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 804310d8c..a429bdf84 100644
--- a/README.md
+++ b/README.md
@@ -43,7 +43,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 
 ## <a name="installation-and-usage"></a>Installation and Usage
 
-Prerequisites: [Node.js](https://nodejs.org/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
+Prerequisites: [Node.js](https://nodejs.org/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
 
 You can install ESLint using npm:
 
@@ -298,7 +298,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
 <p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://coinbase.com"><img src="https://avatars.githubusercontent.com/u/1885080?v=4" alt="Coinbase" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://retool.com/"><img src="https://images.opencollective.com/retool/98ea68e/logo.png" alt="Retool" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
-<p><a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
+<p><a href="https://mobilen.nu"><img src="https://images.opencollective.com/mobilen/e19860d/logo.png" alt="Mobilen" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/f9645d9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a> <a href="https://www.practiceignition.com"><img src="https://avatars.githubusercontent.com/u/5753491?v=4" alt="Practice Ignition" height="32"></a></p>
 <!--sponsorsend-->
 
 ## <a name="technology-sponsors"></a>Technology Sponsors
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index ec53551c5..d21ee7b7a 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>
@@ -1079,14 +1081,12 @@ const isIgnored = cli.isPathIgnored("foo/bar.js");
 Retrieves a formatter, which you can then use to format a report object. The argument is either the name of a built-in formatter:
 
 * "[checkstyle](../user-guide/formatters#checkstyle)"
-* "[codeframe](../user-guide/formatters#codeframe)"
 * "[compact](../user-guide/formatters#compact)"
 * "[html](../user-guide/formatters#html)"
 * "[jslint-xml](../user-guide/formatters#jslint-xml)"
 * "[json](../user-guide/formatters#json)"
 * "[junit](../user-guide/formatters#junit)"
 * "[stylish](../user-guide/formatters#stylish)" (the default)
-* "[table](../user-guide/formatters#table)"
 * "[tap](../user-guide/formatters#tap)"
 * "[unix](../user-guide/formatters#unix)"
 * "[visualstudio](../user-guide/formatters#visualstudio)"
diff --git a/docs/developer-guide/selectors.md b/docs/developer-guide/selectors.md
index afe6600dc..78bb26084 100644
--- a/docs/developer-guide/selectors.md
+++ b/docs/developer-guide/selectors.md
@@ -31,7 +31,7 @@ The following selectors are supported:
 * wildcard (matches all nodes): `*`
 * attribute existence: `[attr]`
 * attribute value: `[attr="foo"]` or `[attr=123]`
-* attribute regex: `[attr=/foo.*/]`
+* attribute regex: `[attr=/foo.*/]` <sub>(with some [known issues](#known-issues))</sub>
 * attribute conditions: `[attr!="foo"]`, `[attr>2]`, `[attr<3]`, `[attr>=2]`, or `[attr<=3]`
 * nested attribute: `[attr.level2="foo"]`
 * field: `FunctionDeclaration > Identifier.id`
@@ -131,3 +131,7 @@ Or you can enforce that calls to `setTimeout` always have two arguments:
 ```
 
 Using selectors in the `no-restricted-syntax` rule can give you a lot of control over problematic patterns in your codebase, without needing to write custom rules to detect each pattern.
+
+### Known issues
+
+Due to a [bug](https://github.com/estools/esquery/issues/68) in [esquery](https://github.com/estools/esquery), regular expressions that contain a forward-slash character `/` aren't properly parsed, so `[value=/some\/path/]` will be a syntax error. As a [workaround](https://github.com/estools/esquery/issues/68), you can replace the `/` character with its unicode counterpart, like so: `[value=/some\\u002Fpath/]`.
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 99fd61eb9..e86611f67 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
@@ -337,14 +338,12 @@ When specified, the given format is output into the provided file name.
 This option specifies the output format for the console. Possible formats are:
 
 * [checkstyle](formatters.md/#checkstyle)
-* [codeframe](formatters.md/#codeframe)
 * [compact](formatters.md/#compact)
 * [html](formatters.md/#html)
 * [jslint-xml](formatters.md/#jslint-xml)
 * [json](formatters.md/#json)
 * [junit](formatters.md/#junit)
 * [stylish](formatters.md/#stylish) (the default)
-* [table](formatters.md/#table)
 * [tap](formatters.md/#tap)
 * [unix](formatters.md/#unix)
 * [visualstudio](formatters.md/#visualstudio)
@@ -467,6 +466,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/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 3b28a91a2..3c9633bb3 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -8,7 +8,7 @@ ESLint is a tool for identifying and reporting on patterns found in ECMAScript/J
 
 ## Installation and Usage
 
-Prerequisites: [Node.js](https://nodejs.org/en/) (`^10.12.0`, or `>=12.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
+Prerequisites: [Node.js](https://nodejs.org/en/) (`^12.22.0`, `^14.17.0`, or `>=16.0.0`) built with SSL support. (If you are using an official Node.js distribution, SSL is always built in.)
 
 You can install ESLint using npm or yarn:
 
diff --git a/lib/cli-engine/cli-engine.js b/lib/cli-engine/cli-engine.js
index ca298f9c3..105de3e55 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
@@ -1013,7 +1019,11 @@ class CLIEngine {
             try {
                 return require(formatterPath);
             } catch (ex) {
-                ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+                if (format === "table" || format === "codeframe") {
+                    ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``;
+                } else {
+                    ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+                }
                 throw ex;
             }
 
diff --git a/lib/cli-engine/formatters/codeframe.js b/lib/cli-engine/formatters/codeframe.js
deleted file mode 100644
index 41e3ab7b0..000000000
--- a/lib/cli-engine/formatters/codeframe.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/**
- * @fileoverview Codeframe reporter
- * @author Vitor Balocco
- */
-"use strict";
-
-const chalk = require("chalk");
-const { codeFrameColumns } = require("@babel/code-frame");
-const path = require("path");
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Given a word and a count, append an s if count is not one.
- * @param   {string} word  A word in its singular form.
- * @param   {number} count A number controlling whether word should be pluralized.
- * @returns {string}       The original word with an s on the end if count is not one.
- */
-function pluralize(word, count) {
-    return (count === 1 ? word : `${word}s`);
-}
-
-/**
- * Gets a formatted relative file path from an absolute path and a line/column in the file.
- * @param   {string} filePath The absolute file path to format.
- * @param   {number} line     The line from the file to use for formatting.
- * @param   {number} column   The column from the file to use for formatting.
- * @returns {string}          The formatted file path.
- */
-function formatFilePath(filePath, line, column) {
-    let relPath = path.relative(process.cwd(), filePath);
-
-    if (line && column) {
-        relPath += `:${line}:${column}`;
-    }
-
-    return chalk.green(relPath);
-}
-
-/**
- * Gets the formatted output for a given message.
- * @param   {Object} message      The object that represents this message.
- * @param   {Object} parentResult The result object that this message belongs to.
- * @returns {string}              The formatted output.
- */
-function formatMessage(message, parentResult) {
-    const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
-    const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/u, "$1"))}`;
-    const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
-    const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
-    const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
-
-    const firstLine = [
-        `${type}:`,
-        `${msg}`,
-        ruleId ? `${ruleId}` : "",
-        sourceCode ? `at ${filePath}:` : `at ${filePath}`
-    ].filter(String).join(" ");
-
-    const result = [firstLine];
-
-    if (sourceCode) {
-        result.push(
-            codeFrameColumns(sourceCode, { start: { line: message.line, column: message.column } }, { highlightCode: false })
-        );
-    }
-
-    return result.join("\n");
-}
-
-/**
- * Gets the formatted output summary for a given number of errors and warnings.
- * @param   {number} errors   The number of errors.
- * @param   {number} warnings The number of warnings.
- * @param   {number} fixableErrors The number of fixable errors.
- * @param   {number} fixableWarnings The number of fixable warnings.
- * @returns {string}          The formatted output summary.
- */
-function formatSummary(errors, warnings, fixableErrors, fixableWarnings) {
-    const summaryColor = errors > 0 ? "red" : "yellow";
-    const summary = [];
-    const fixablesSummary = [];
-
-    if (errors > 0) {
-        summary.push(`${errors} ${pluralize("error", errors)}`);
-    }
-
-    if (warnings > 0) {
-        summary.push(`${warnings} ${pluralize("warning", warnings)}`);
-    }
-
-    if (fixableErrors > 0) {
-        fixablesSummary.push(`${fixableErrors} ${pluralize("error", fixableErrors)}`);
-    }
-
-    if (fixableWarnings > 0) {
-        fixablesSummary.push(`${fixableWarnings} ${pluralize("warning", fixableWarnings)}`);
-    }
-
-    let output = chalk[summaryColor].bold(`${summary.join(" and ")} found.`);
-
-    if (fixableErrors || fixableWarnings) {
-        output += chalk[summaryColor].bold(`\n${fixablesSummary.join(" and ")} potentially fixable with the \`--fix\` option.`);
-    }
-
-    return output;
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = function(results) {
-    let errors = 0;
-    let warnings = 0;
-    let fixableErrors = 0;
-    let fixableWarnings = 0;
-
-    const resultsWithMessages = results.filter(result => result.messages.length > 0);
-
-    let output = resultsWithMessages.reduce((resultsOutput, result) => {
-        const messages = result.messages.map(message => `${formatMessage(message, result)}\n\n`);
-
-        errors += result.errorCount;
-        warnings += result.warningCount;
-        fixableErrors += result.fixableErrorCount;
-        fixableWarnings += result.fixableWarningCount;
-
-        return resultsOutput.concat(messages);
-    }, []).join("\n");
-
-    output += "\n";
-    output += formatSummary(errors, warnings, fixableErrors, fixableWarnings);
-
-    return (errors + warnings) > 0 ? output : "";
-};
diff --git a/lib/cli-engine/formatters/table.js b/lib/cli-engine/formatters/table.js
deleted file mode 100644
index a74cce0d5..000000000
--- a/lib/cli-engine/formatters/table.js
+++ /dev/null
@@ -1,159 +0,0 @@
-/**
- * @fileoverview "table reporter.
- * @author Gajus Kuizinas <gajus@gajus.com>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const chalk = require("chalk"),
-    table = require("table").table;
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Given a word and a count, append an "s" if count is not one.
- * @param {string} word A word.
- * @param {number} count Quantity.
- * @returns {string} The original word with an s on the end if count is not one.
- */
-function pluralize(word, count) {
-    return (count === 1 ? word : `${word}s`);
-}
-
-/**
- * Draws text table.
- * @param {Array<Object>} messages Error messages relating to a specific file.
- * @returns {string} A text table.
- */
-function drawTable(messages) {
-    const rows = [];
-
-    if (messages.length === 0) {
-        return "";
-    }
-
-    rows.push([
-        chalk.bold("Line"),
-        chalk.bold("Column"),
-        chalk.bold("Type"),
-        chalk.bold("Message"),
-        chalk.bold("Rule ID")
-    ]);
-
-    messages.forEach(message => {
-        let messageType;
-
-        if (message.fatal || message.severity === 2) {
-            messageType = chalk.red("error");
-        } else {
-            messageType = chalk.yellow("warning");
-        }
-
-        rows.push([
-            message.line || 0,
-            message.column || 0,
-            messageType,
-            message.message,
-            message.ruleId || ""
-        ]);
-    });
-
-    return table(rows, {
-        columns: {
-            0: {
-                width: 8,
-                wrapWord: true
-            },
-            1: {
-                width: 8,
-                wrapWord: true
-            },
-            2: {
-                width: 8,
-                wrapWord: true
-            },
-            3: {
-                paddingRight: 5,
-                width: 50,
-                wrapWord: true
-            },
-            4: {
-                width: 20,
-                wrapWord: true
-            }
-        },
-        drawHorizontalLine(index) {
-            return index === 1;
-        }
-    });
-}
-
-/**
- * Draws a report (multiple tables).
- * @param {Array} results Report results for every file.
- * @returns {string} A column of text tables.
- */
-function drawReport(results) {
-    let files;
-
-    files = results.map(result => {
-        if (!result.messages.length) {
-            return "";
-        }
-
-        return `\n${result.filePath}\n\n${drawTable(result.messages)}`;
-    });
-
-    files = files.filter(content => content.trim());
-
-    return files.join("");
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-module.exports = function(report) {
-    let result,
-        errorCount,
-        warningCount;
-
-    result = "";
-    errorCount = 0;
-    warningCount = 0;
-
-    report.forEach(fileReport => {
-        errorCount += fileReport.errorCount;
-        warningCount += fileReport.warningCount;
-    });
-
-    if (errorCount || warningCount) {
-        result = drawReport(report);
-    }
-
-    result += `\n${table([
-        [
-            chalk.red(pluralize(`${errorCount} Error`, errorCount))
-        ],
-        [
-            chalk.yellow(pluralize(`${warningCount} Warning`, warningCount))
-        ]
-    ], {
-        columns: {
-            0: {
-                width: 110,
-                wrapWord: true
-            }
-        },
-        drawHorizontalLine() {
-            return true;
-        }
-    })}`;
-
-    return result;
-};
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/lib/rules/curly.js b/lib/rules/curly.js
index 92d31a647..61dcd8024 100644
--- a/lib/rules/curly.js
+++ b/lib/rules/curly.js
@@ -131,15 +131,6 @@ module.exports = {
             return token.value === "else" && token.type === "Keyword";
         }
 
-        /**
-         * Gets the `else` keyword token of a given `IfStatement` node.
-         * @param {ASTNode} node A `IfStatement` node to get.
-         * @returns {Token} The `else` keyword token.
-         */
-        function getElseKeyword(node) {
-            return node.alternate && sourceCode.getFirstTokenBetween(node.consequent, node.alternate, isElseKeywordToken);
-        }
-
         /**
          * Determines whether the given node has an `else` keyword token as the first token after.
          * @param {ASTNode} node The node to check.
@@ -361,7 +352,7 @@ module.exports = {
                         if (this.expected) {
                             context.report({
                                 node,
-                                loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
+                                loc: body.loc,
                                 messageId: opts && opts.condition ? "missingCurlyAfterCondition" : "missingCurlyAfter",
                                 data: {
                                     name
@@ -371,7 +362,7 @@ module.exports = {
                         } else {
                             context.report({
                                 node,
-                                loc: (name !== "else" ? node : getElseKeyword(node)).loc.start,
+                                loc: body.loc,
                                 messageId: opts && opts.condition ? "unexpectedCurlyAfterCondition" : "unexpectedCurlyAfter",
                                 data: {
                                     name
diff --git a/lib/shared/relative-module-resolver.js b/lib/shared/relative-module-resolver.js
index cd743f379..fcc2ac41b 100644
--- a/lib/shared/relative-module-resolver.js
+++ b/lib/shared/relative-module-resolver.js
@@ -17,14 +17,7 @@
 
 "use strict";
 
-const Module = require("module");
-
-/*
- * `Module.createRequire` is added in v12.2.0. It supports URL as well.
- * We only support the case where the argument is a filepath, not a URL.
- */
-// eslint-disable-next-line node/no-unsupported-features/node-builtins, node/no-deprecated-api
-const createRequire = Module.createRequire || Module.createRequireFromPath;
+const { createRequire } = require("module");
 
 module.exports = {
 
diff --git a/package.json b/package.json
index ea08a90ef..8ef283a07 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "7.31.0",
+  "version": "7.32.0",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {
@@ -43,7 +43,6 @@
   "homepage": "https://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
-    "@babel/code-frame": "7.12.11",
     "@eslint/eslintrc": "^0.4.3",
     "@humanwhocodes/config-array": "^0.5.0",
     "ajv": "^6.10.0",
@@ -80,7 +79,6 @@
     "semver": "^7.2.1",
     "strip-ansi": "^6.0.0",
     "strip-json-comments": "^3.1.0",
-    "table": "^6.0.9",
     "text-table": "^0.2.0",
     "v8-compile-cache": "^2.0.3"
   },
@@ -96,14 +94,14 @@
     "ejs": "^3.0.2",
     "eslint": "file:.",
     "eslint-config-eslint": "file:packages/eslint-config-eslint",
-    "eslint-plugin-eslint-plugin": "^3.4.0",
+    "eslint-plugin-eslint-plugin": "^3.5.3",
     "eslint-plugin-internal-rules": "file:tools/internal-rules",
     "eslint-plugin-jsdoc": "^25.4.3",
     "eslint-plugin-node": "^11.1.0",
     "eslint-release": "^2.0.0",
     "eslump": "^3.0.0",
     "esprima": "^4.0.1",
-    "fs-teardown": "0.1.1",
+    "fs-teardown": "^0.1.3",
     "glob": "^7.1.6",
     "jsdoc": "^3.5.5",
     "karma": "^6.1.1",
@@ -141,6 +139,6 @@
   ],
   "license": "MIT",
   "engines": {
-    "node": "^10.12.0 || >=12.0.0"
+    "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
   }
 }
diff --git a/templates/formatter-examples.md.ejs b/templates/formatter-examples.md.ejs
index 3231ea44d..b5583957f 100644
--- a/templates/formatter-examples.md.ejs
+++ b/templates/formatter-examples.md.ejs
@@ -6,7 +6,7 @@ layout: doc
 
 ESLint comes with several built-in formatters to control the appearance of the linting results, and supports third-party formatters as well.
 
-You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format codeframe` uses the `codeframe` formatter.
+You can specify a formatter using the `--format` or `-f` flag on the command line. For example, `--format json` uses the `json` formatter.
 
 The built-in formatter options are:
 
diff --git a/tools/internal-rules/consistent-meta-messages.js b/tools/internal-rules/consistent-meta-messages.js
deleted file mode 100644
index b094c86e8..000000000
--- a/tools/internal-rules/consistent-meta-messages.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/**
- * @fileoverview A rule to enforce using `meta.messages` property in core rules
- * @author 薛定谔的猫<hh_2013@foxmail.com>
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Gets the property of the Object node passed in that has the name specified.
- * @param {string} property Name of the property to return.
- * @param {ASTNode} node The ObjectExpression node.
- * @returns {ASTNode} The Property node or null if not found.
- */
-function getPropertyFromObject(property, node) {
-    const properties = node.properties;
-
-    for (let i = 0; i < properties.length; i++) {
-        if (properties[i].key.name === property) {
-            return properties[i];
-        }
-    }
-
-    return null;
-}
-
-/**
- * Verifies that the meta.messages property is present.
- * TODO: check it has the correct value
- * @param {RuleContext} context The ESLint rule context.
- * @param {ASTNode} exportsNode ObjectExpression node that the rule exports.
- * @returns {void}
- */
-function checkMetaMessages(context, exportsNode) {
-    if (exportsNode.type !== "ObjectExpression") {
-
-        // if the exported node is not the correct format, "internal-no-invalid-meta" will already report this.
-        return;
-    }
-
-    const metaProperty = getPropertyFromObject("meta", exportsNode);
-    const messages = metaProperty && getPropertyFromObject("messages", metaProperty.value);
-
-    if (!messages) {
-        context.report({
-            node: metaProperty,
-            messageId: "expectedMessages"
-        });
-    }
-}
-
-//------------------------------------------------------------------------------
-// Rule Definition
-//------------------------------------------------------------------------------
-
-module.exports = {
-    meta: {
-        docs: {
-            description: "enforce using `meta.messages` property in core rules",
-            category: "Internal",
-            recommended: false
-        },
-        schema: [],
-        type: "suggestion",
-        messages: {
-            expectedMessages: "Expected `meta.messages` property."
-        }
-    },
-
-    create(context) {
-        return {
-            "AssignmentExpression[left.object.name='module'][left.property.name='exports']"(node) {
-                checkMetaMessages(context, node.right);
-            }
-        };
-    }
-};


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..996778822 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"
@@ -4628,7 +4643,7 @@ describe("CLIEngine", () => {
             assert.isFunction(formatter);
         });
 
-        it("should return null when a customer formatter doesn't exist", () => {
+        it("should return null when a custom formatter doesn't exist", () => {
             const engine = new CLIEngine(),
                 formatterPath = getFixturePath("formatters", "doesntexist.js"),
                 fullFormatterPath = path.resolve(formatterPath);
@@ -4647,6 +4662,18 @@ describe("CLIEngine", () => {
             }, `There was a problem loading formatter: ${fullFormatterPath}\nError: Cannot find module '${fullFormatterPath}'`);
         });
 
+        it("should throw when a built-in formatter no longer exists", () => {
+            const engine = new CLIEngine();
+
+            assert.throws(() => {
+                engine.getFormatter("table");
+            }, "The table formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-table`");
+
+            assert.throws(() => {
+                engine.getFormatter("codeframe");
+            }, "The codeframe formatter is no longer part of core ESLint. Install it manually with `npm install -D eslint-formatter-codeframe`");
+        });
+
         it("should throw if the required formatter exists but has an error", () => {
             const engine = new CLIEngine(),
                 formatterPath = getFixturePath("formatters", "broken.js");
@@ -5077,6 +5104,7 @@ describe("CLIEngine", () => {
                             ],
                             errorCount: 1,
                             warningCount: 0,
+                            fatalErrorCount: 0,
                             fixableErrorCount: 0,
                             fixableWarningCount: 0,
                             source: "/* eslint-disable */"
@@ -5084,6 +5112,7 @@ describe("CLIEngine", () => {
                     ],
                     errorCount: 1,
                     warningCount: 0,
+                    fatalErrorCount: 0,
                     fixableErrorCount: 0,
                     fixableWarningCount: 0,
                     usedDeprecatedRules: []
@@ -6155,7 +6184,8 @@ describe("CLIEngine", () => {
                             }
                         ],
                         source: "a == b",
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6177,7 +6207,8 @@ describe("CLIEngine", () => {
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6223,7 +6254,8 @@ describe("CLIEngine", () => {
                         fixableErrorCount: 0,
                         fixableWarningCount: 0,
                         messages: [],
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
@@ -6258,7 +6290,8 @@ describe("CLIEngine", () => {
                             }
                         ],
                         source: "a == b",
-                        warningCount: 0
+                        warningCount: 0,
+                        fatalErrorCount: 0
                     }
                 ]);
             });
diff --git a/tests/lib/cli-engine/formatters/codeframe.js b/tests/lib/cli-engine/formatters/codeframe.js
deleted file mode 100644
index 398d6b8c7..000000000
--- a/tests/lib/cli-engine/formatters/codeframe.js
+++ /dev/null
@@ -1,483 +0,0 @@
-/**
- * @fileoverview Tests for codeframe reporter.
- * @author Vitor Balocco
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert;
-const sinon = require("sinon");
-const proxyquire = require("proxyquire");
-const chalk = require("chalk");
-const path = require("path");
-const stripAnsi = require("strip-ansi");
-
-// Chalk protects its methods so we need to inherit from it for Sinon to work.
-const chalkStub = Object.create(chalk, {
-    yellow: {
-        value(str) {
-            return chalk.yellow(str);
-        },
-        writable: true
-    },
-    red: {
-        value(str) {
-            return chalk.red(str);
-        },
-        writable: true
-    }
-});
-
-chalkStub.yellow.bold = chalk.yellow.bold;
-chalkStub.red.bold = chalk.red.bold;
-
-const formatter = proxyquire("../../../../lib/cli-engine/formatters/codeframe", { chalk: chalkStub });
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("formatter:codeframe", () => {
-    afterEach(() => {
-        sinon.verifyAndRestore();
-    });
-
-    describe("when passed no messages", () => {
-        const code = [{
-            filePath: "foo.js",
-            messages: [],
-            errorCount: 0,
-            warningCount: 0
-        }];
-
-        it("should return nothing", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(result, "");
-        });
-    });
-
-    describe("when passed a single warning message", () => {
-        const code = [{
-            filePath: path.join(process.cwd(), "lib", "foo.js"),
-            source: "var foo = 1;\n var bar = 2;\n",
-            messages: [{
-                message: "Unexpected foo.",
-                severity: 1,
-                line: 1,
-                column: 5,
-                ruleId: "foo"
-            }],
-            errorCount: 0,
-            warningCount: 1,
-            fixableErrorCount: 0,
-            fixableWarningCount: 0
-        }];
-
-        it("should return a string in the correct format for warnings", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), [
-                `warning: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`,
-                "> 1 | var foo = 1;",
-                "    |     ^",
-                "  2 |  var bar = 2;",
-                "  3 | ",
-                "\n",
-                "1 warning found."
-            ].join("\n"));
-        });
-
-        it("should return bold yellow summary when there are only warnings", () => {
-            sinon.spy(chalkStub.yellow, "bold");
-            sinon.spy(chalkStub.red, "bold");
-
-            formatter(code);
-
-            assert.strictEqual(chalkStub.yellow.bold.callCount, 1);
-            assert.strictEqual(chalkStub.red.bold.callCount, 0);
-        });
-
-        describe("when the warning is fixable", () => {
-            beforeEach(() => {
-                code[0].fixableWarningCount = 1;
-            });
-
-            it("should return a string in the correct format", () => {
-                const result = formatter(code);
-
-                assert.strictEqual(stripAnsi(result), [
-                    `warning: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`,
-                    "> 1 | var foo = 1;",
-                    "    |     ^",
-                    "  2 |  var bar = 2;",
-                    "  3 | ",
-                    "\n",
-                    "1 warning found.",
-                    "1 warning potentially fixable with the `--fix` option."
-                ].join("\n"));
-            });
-        });
-    });
-
-    describe("when passed a single error message", () => {
-        const code = [{
-            filePath: path.join(process.cwd(), "lib", "foo.js"),
-            source: "var foo = 1;\n var bar = 2;\n",
-            messages: [{
-                message: "Unexpected foo.",
-                severity: 2,
-                line: 1,
-                column: 5,
-                ruleId: "foo"
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }];
-
-        it("should return a string in the correct format for errors", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), [
-                `error: Unexpected foo (foo) at ${path.join("lib", "foo.js")}:1:5:`,
-                "> 1 | var foo = 1;",
-                "    |     ^",
-                "  2 |  var bar = 2;",
-                "  3 | ",
-                "\n",
-                "1 error found."
-            ].join("\n"));
-        });
-
-        it("should return bold red summary when there are errors", () => {
-            sinon.spy(chalkStub.yellow, "bold");
-            sinon.spy(chalkStub.red, "bold");
-
-            formatter(code);
-
-            assert.strictEqual(chalkStub.yellow.bold.callCount, 0);
-            assert.strictEqual(chalkStub.red.bold.callCount, 1);
-        });
-    });
-
-    describe("when passed a message that ends with ' .'", () => {
-        const code = [{
-            filePath: "foo.js",
-            messages: [{
-                ruleId: "foo",
-                message: "Unexpected .",
-                severity: 2,
-                source: "foo"
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }];
-
-        it("should return a string in the correct format (retaining the ' .')", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), "error: Unexpected . (foo) at foo.js\n\n\n1 error found.");
-        });
-    });
-
-    describe("when passed multiple messages", () => {
-        const code = [{
-            filePath: "foo.js",
-            source: "const foo = 1\n",
-            messages: [{
-                message: "Missing semicolon.",
-                severity: 2,
-                line: 1,
-                column: 14,
-                ruleId: "semi"
-            }, {
-                message: "'foo' is assigned a value but never used.",
-                severity: 2,
-                line: 1,
-                column: 7,
-                ruleId: "no-unused-vars"
-            }],
-            errorCount: 2,
-            warningCount: 0
-        }];
-
-        it("should return a string with multiple entries", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), [
-                "error: Missing semicolon (semi) at foo.js:1:14:",
-                "> 1 | const foo = 1",
-                "    |              ^",
-                "  2 | ",
-                "\n",
-                "error: 'foo' is assigned a value but never used (no-unused-vars) at foo.js:1:7:",
-                "> 1 | const foo = 1",
-                "    |       ^",
-                "  2 | ",
-                "\n",
-                "2 errors found."
-            ].join("\n"));
-        });
-
-        it("should return bold red summary when at least 1 of the messages is an error", () => {
-            sinon.spy(chalkStub.yellow, "bold");
-            sinon.spy(chalkStub.red, "bold");
-            code[0].messages[0].severity = 1;
-            code[0].warningCount = 1;
-            code[0].errorCount = 1;
-
-            formatter(code);
-
-            assert.strictEqual(chalkStub.yellow.bold.callCount, 0);
-            assert.strictEqual(chalkStub.red.bold.callCount, 1);
-        });
-    });
-
-    describe("when passed one file with 1 message and fixes applied", () => {
-        const code = [{
-            filePath: "foo.js",
-            messages: [{
-                ruleId: "no-unused-vars",
-                severity: 2,
-                message: "'foo' is assigned a value but never used.",
-                line: 4,
-                column: 11,
-                source: "    const foo = 1;"
-            }],
-            errorCount: 1,
-            warningCount: 0,
-            output: "function foo() {\n\n    // a comment\n    const foo = 1;\n}\n\n"
-        }];
-
-        it("should return a string with code preview pointing to the correct location after fixes", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), [
-                "error: 'foo' is assigned a value but never used (no-unused-vars) at foo.js:4:11:",
-                "  2 | ",
-                "  3 |     // a comment",
-                "> 4 |     const foo = 1;",
-                "    |           ^",
-                "  5 | }",
-                "  6 | ",
-                "  7 | ",
-                "\n",
-                "1 error found."
-            ].join("\n"));
-        });
-    });
-
-    describe("when passed multiple files with 1 message each", () => {
-        const code = [{
-            filePath: "foo.js",
-            source: "const foo = 1\n",
-            messages: [{
-                message: "Missing semicolon.",
-                severity: 2,
-                line: 1,
-                column: 14,
-                ruleId: "semi"
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }, {
-            filePath: "bar.js",
-            source: "const bar = 2\n",
-            messages: [{
-                message: "Missing semicolon.",
-                severity: 2,
-                line: 1,
-                column: 14,
-                ruleId: "semi"
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }];
-
-        it("should return a string with multiple entries", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), [
-                "error: Missing semicolon (semi) at foo.js:1:14:",
-                "> 1 | const foo = 1",
-                "    |              ^",
-                "  2 | ",
-                "\n",
-                "error: Missing semicolon (semi) at bar.js:1:14:",
-                "> 1 | const bar = 2",
-                "    |              ^",
-                "  2 | ",
-                "\n",
-                "2 errors found."
-            ].join("\n"));
-        });
-    });
-
-    describe("when passed a fatal error message", () => {
-        const code = [{
-            filePath: "foo.js",
-            source: "e{}\n",
-            messages: [{
-                ruleId: null,
-                fatal: true,
-                severity: 2,
-                source: "e{}",
-                message: "Parsing error: Unexpected token {",
-                line: 1,
-                column: 2
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }];
-
-        it("should return a string in the correct format", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), [
-                "error: Parsing error: Unexpected token { at foo.js:1:2:",
-                "> 1 | e{}",
-                "    |  ^",
-                "  2 | ",
-                "\n",
-                "1 error found."
-            ].join("\n"));
-        });
-    });
-
-    describe("when passed one file not found message", () => {
-        const code = [{
-            filePath: "foo.js",
-            messages: [{
-                fatal: true,
-                message: "Couldn't find foo.js."
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }];
-
-        it("should return a string without code preview (codeframe)", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), "error: Couldn't find foo.js at foo.js\n\n\n1 error found.");
-        });
-    });
-
-    describe("when passed a single message with no line or column", () => {
-        const code = [{
-            filePath: "foo.js",
-            messages: [{
-                ruleId: "foo",
-                message: "Unexpected foo.",
-                severity: 2,
-                source: "foo"
-            }],
-            errorCount: 1,
-            warningCount: 0
-        }];
-
-        it("should return a string without code preview (codeframe)", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), "error: Unexpected foo (foo) at foo.js\n\n\n1 error found.");
-        });
-
-        it("should output filepath but without 'line:column' appended", () => {
-            const result = formatter(code);
-
-            assert.strictEqual(stripAnsi(result), "error: Unexpected foo (foo) at foo.js\n\n\n1 error found.");
-        });
-    });
-
-
-    describe("fixable problems", () => {
-        it("should not output fixable problems message when no errors or warnings are fixable", () => {
-            const code = [{
-                filePath: "foo.js",
-                errorCount: 1,
-                warningCount: 0,
-                fixableErrorCount: 0,
-                fixableWarningCount: 0,
-                messages: [{
-                    message: "Unexpected foo.",
-                    severity: 2,
-                    line: 5,
-                    column: 10,
-                    ruleId: "foo"
-                }]
-            }];
-
-            const result = formatter(code);
-
-            assert.notInclude(result, "potentially fixable");
-        });
-
-        it("should output the fixable problems message when errors are fixable", () => {
-            const code = [{
-                filePath: "foo.js",
-                errorCount: 1,
-                warningCount: 0,
-                fixableErrorCount: 1,
-                fixableWarningCount: 0,
-                messages: [{
-                    message: "Unexpected foo.",
-                    severity: 2,
-                    line: 5,
-                    column: 10,
-                    ruleId: "foo"
-                }]
-            }];
-
-            const result = formatter(code);
-
-            assert.include(result, "1 error potentially fixable with the `--fix` option.");
-        });
-
-        it("should output fixable problems message when warnings are fixable", () => {
-            const code = [{
-                filePath: "foo.js",
-                errorCount: 0,
-                warningCount: 3,
-                fixableErrorCount: 0,
-                fixableWarningCount: 2,
-                messages: [{
-                    message: "Unexpected foo."
-                }]
-            }];
-
-            const result = formatter(code);
-
-            assert.include(result, "2 warnings potentially fixable with the `--fix` option.");
-        });
-
-        it("should output the total number of fixable errors and warnings", () => {
-            const code = [{
-                filePath: "foo.js",
-                errorCount: 5,
-                warningCount: 3,
-                fixableErrorCount: 5,
-                fixableWarningCount: 2,
-                messages: [{
-                    message: "Unexpected foo."
-                }]
-            }, {
-                filePath: "bar.js",
-                errorCount: 4,
-                warningCount: 2,
-                fixableErrorCount: 4,
-                fixableWarningCount: 1,
-                messages: [{
-                    message: "Unexpected bar."
-                }]
-            }];
-
-            const result = formatter(code);
-
-            assert.include(result, "9 errors and 3 warnings potentially fixable with the `--fix` option.");
-        });
-    });
-
-});
diff --git a/tests/lib/cli-engine/formatters/table.js b/tests/lib/cli-engine/formatters/table.js
deleted file mode 100644
index 3e29caf2e..000000000
--- a/tests/lib/cli-engine/formatters/table.js
+++ /dev/null
@@ -1,324 +0,0 @@
-/**
- * @fileoverview Tests for "table" reporter.
- * @author Gajus Kuizinas <gajus@gajus.com>
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("chai").assert;
-const chalk = require("chalk");
-const formatter = require("../../../../lib/cli-engine/formatters/table");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("formatter:table", () => {
-    const originalColorLevel = chalk.level;
-
-    before(() => {
-        chalk.level = 0;
-    });
-
-    after(() => {
-        chalk.level = originalColorLevel;
-    });
-
-    describe("when passed no messages", () => {
-        const code = [
-            {
-                filePath: "foo.js",
-                messages: [],
-                errorCount: 0,
-                warningCount: 0
-            }
-        ];
-
-        it("should return a table of error and warning count with no messages", () => {
-            const expectedOutput = [
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 0 Errors                                                                                                       ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 0 Warnings                                                                                                     ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-    });
-
-    describe("when passed a single message", () => {
-        it("should return a string in the correct format for errors", () => {
-            const code = [
-                {
-                    filePath: "foo.js",
-                    messages: [
-                        {
-                            message: "Unexpected foo.",
-                            severity: 2,
-                            line: 5,
-                            column: 10,
-                            ruleId: "foo"
-                        }
-                    ],
-                    errorCount: 1,
-                    warningCount: 0
-                }
-            ];
-
-            const expectedOutput = [
-                "",
-                "foo.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 5        │ 10       │ error    │ Unexpected foo.                                        │ foo                  ║",
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 1 Error                                                                                                        ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 0 Warnings                                                                                                     ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-
-        it("should return a string in the correct format for warnings", () => {
-            const code = [
-                {
-                    filePath: "foo.js",
-                    messages: [
-                        {
-                            message: "Unexpected foo.",
-                            severity: 1,
-                            line: 5,
-                            column: 10,
-                            ruleId: "foo"
-                        }
-                    ],
-                    errorCount: 0,
-                    warningCount: 1
-                }
-            ];
-
-            const expectedOutput = [
-                "",
-                "foo.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 5        │ 10       │ warning  │ Unexpected foo.                                        │ foo                  ║",
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 0 Errors                                                                                                       ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 1 Warning                                                                                                      ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-    });
-
-    describe("when passed a fatal error message", () => {
-        it("should return a string in the correct format", () => {
-            const code = [
-                {
-                    filePath: "foo.js",
-                    messages: [
-                        {
-                            fatal: true,
-                            message: "Unexpected foo.",
-                            line: 5,
-                            column: 10,
-                            ruleId: "foo"
-                        }
-                    ],
-                    errorCount: 1,
-                    warningCount: 0
-                }
-            ];
-
-            const expectedOutput = [
-                "",
-                "foo.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 5        │ 10       │ error    │ Unexpected foo.                                        │ foo                  ║",
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 1 Error                                                                                                        ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 0 Warnings                                                                                                     ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-    });
-
-    describe("when passed multiple messages", () => {
-        it("should return a string with multiple entries", () => {
-            const code = [
-                {
-                    filePath: "foo.js",
-                    messages: [
-                        {
-                            message: "Unexpected foo.",
-                            severity: 2,
-                            line: 5,
-                            column: 10,
-                            ruleId: "foo"
-                        },
-                        {
-                            message: "Unexpected bar.",
-                            severity: 1,
-                            line: 6,
-                            column: 11,
-                            ruleId: "bar"
-                        }
-                    ],
-                    errorCount: 1,
-                    warningCount: 1
-                }
-            ];
-
-            const expectedOutput = [
-                "",
-                "foo.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 5        │ 10       │ error    │ Unexpected foo.                                        │ foo                  ║",
-                "║ 6        │ 11       │ warning  │ Unexpected bar.                                        │ bar                  ║",
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 1 Error                                                                                                        ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 1 Warning                                                                                                      ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-    });
-
-    describe("when passed multiple files with 1 message each", () => {
-        it("should return a string with multiple entries", () => {
-            const code = [
-                {
-                    filePath: "foo.js",
-                    messages: [
-                        {
-                            message: "Unexpected foo.",
-                            severity: 2,
-                            line: 5,
-                            column: 10,
-                            ruleId: "foo"
-                        }
-                    ],
-                    errorCount: 1,
-                    warningCount: 0
-                }, {
-                    filePath: "bar.js",
-                    messages: [
-                        {
-                            message: "Unexpected bar.",
-                            severity: 1,
-                            line: 6,
-                            column: 11,
-                            ruleId: "bar"
-                        }
-                    ],
-                    errorCount: 0,
-                    warningCount: 1
-                }
-            ];
-
-            const expectedOutput = [
-                "",
-                "foo.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 5        │ 10       │ error    │ Unexpected foo.                                        │ foo                  ║",
-                "",
-                "bar.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 6        │ 11       │ warning  │ Unexpected bar.                                        │ bar                  ║",
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 1 Error                                                                                                        ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 1 Warning                                                                                                      ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-    });
-
-    describe("when passed one file not found message", () => {
-        it("should return a string without line and column (0, 0)", () => {
-            const code = [
-                {
-                    filePath: "foo.js",
-                    messages: [
-                        {
-                            fatal: true,
-                            message: "Couldn't find foo.js."
-                        }
-                    ],
-                    errorCount: 1,
-                    warningCount: 0
-                }
-            ];
-
-            const expectedOutput = [
-                "",
-                "foo.js",
-                "",
-                "║ Line     │ Column   │ Type     │ Message                                                │ Rule ID              ║",
-                "╟──────────┼──────────┼──────────┼────────────────────────────────────────────────────────┼──────────────────────╢",
-                "║ 0        │ 0        │ error    │ Couldn't find foo.js.                                  │                      ║",
-                "",
-                "╔════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗",
-                "║ 1 Error                                                                                                        ║",
-                "╟────────────────────────────────────────────────────────────────────────────────────────────────────────────────╢",
-                "║ 0 Warnings                                                                                                     ║",
-                "╚════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝",
-                ""
-            ].join("\n");
-
-            const result = formatter(code);
-
-            assert.strictEqual(result, expectedOutput);
-        });
-    });
-});
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..02b518570 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",
@@ -4653,7 +4662,7 @@ describe("ESLint", () => {
             assert.strictEqual(typeof formatter.format, "function");
         });
 
-        it("should throw if a customer formatter doesn't exist", async () => {
+        it("should throw if a custom formatter doesn't exist", async () => {
             const engine = new ESLint();
             const formatterPath = getFixturePath("formatters", "doesntexist.js");
             const fullFormatterPath = path.resolve(formatterPath);
@@ -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/lib/rules/curly.js b/tests/lib/rules/curly.js
index 155eec994..c8121fe5d 100644
--- a/tests/lib/rules/curly.js
+++ b/tests/lib/rules/curly.js
@@ -429,7 +429,26 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfterCondition",
                     data: { name: "if" },
-                    type: "IfStatement"
+                    type: "IfStatement",
+                    line: 1,
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 15
+                }
+            ]
+        },
+        {
+            code: "if (foo) \n bar()",
+            output: "if (foo) \n {bar()}",
+            errors: [
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "if" },
+                    type: "IfStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 7
                 }
             ]
         },
@@ -462,7 +481,26 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfterCondition",
                     data: { name: "while" },
-                    type: "WhileStatement"
+                    type: "WhileStatement",
+                    line: 1,
+                    column: 13,
+                    endLine: 1,
+                    endColumn: 18
+                }
+            ]
+        },
+        {
+            code: "while (foo) \n bar()",
+            output: "while (foo) \n {bar()}",
+            errors: [
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "while" },
+                    type: "WhileStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 7
                 }
             ]
         },
@@ -473,7 +511,26 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfter",
                     data: { name: "do" },
-                    type: "DoWhileStatement"
+                    type: "DoWhileStatement",
+                    line: 1,
+                    column: 4,
+                    endLine: 1,
+                    endColumn: 10
+                }
+            ]
+        },
+        {
+            code: "do \n bar(); while (foo)",
+            output: "do \n {bar();} while (foo)",
+            errors: [
+                {
+                    messageId: "missingCurlyAfter",
+                    data: { name: "do" },
+                    type: "DoWhileStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 8
                 }
             ]
         },
@@ -507,7 +564,93 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfter",
                     data: { name: "for-of" },
-                    type: "ForOfStatement"
+                    type: "ForOfStatement",
+                    line: 1,
+                    column: 22,
+                    endLine: 1,
+                    endColumn: 38
+                }
+            ]
+        },
+        {
+            code: "for (var foo of bar) \n console.log(foo)",
+            output: "for (var foo of bar) \n {console.log(foo)}",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "missingCurlyAfter",
+                    data: { name: "for-of" },
+                    type: "ForOfStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 18
+                }
+            ]
+        },
+        {
+            code: "for (a;;) console.log(foo)",
+            output: "for (a;;) {console.log(foo)}",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "for" },
+                    type: "ForStatement",
+                    line: 1,
+                    column: 11,
+                    endLine: 1,
+                    endColumn: 27
+                }
+            ]
+        },
+        {
+            code: "for (a;;) \n console.log(foo)",
+            output: "for (a;;) \n {console.log(foo)}",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "for" },
+                    type: "ForStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 18
+                }
+            ]
+        },
+        {
+            code: "for (var foo of bar) {console.log(foo)}",
+            output: "for (var foo of bar) console.log(foo)",
+            options: ["multi"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfter",
+                    data: { name: "for-of" },
+                    type: "ForOfStatement",
+                    line: 1,
+                    column: 22,
+                    endLine: 1,
+                    endColumn: 40
+                }
+            ]
+        },
+        {
+            code: "do{foo();} while(bar);",
+            output: "do foo(); while(bar);",
+            options: ["multi"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfter",
+                    data: { name: "do" },
+                    type: "DoWhileStatement",
+                    line: 1,
+                    column: 3,
+                    endLine: 1,
+                    endColumn: 11
                 }
             ]
         },
@@ -519,7 +662,26 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "unexpectedCurlyAfterCondition",
                     data: { name: "for" },
-                    type: "ForStatement"
+                    type: "ForStatement",
+                    line: 1,
+                    column: 13,
+                    endLine: 1,
+                    endColumn: 22
+                }
+            ]
+        },
+        {
+            code: "for (;foo;) \n bar()",
+            output: "for (;foo;) \n {bar()}",
+            errors: [
+                {
+                    data: { name: "for" },
+                    type: "ForStatement",
+                    messageId: "missingCurlyAfterCondition",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 7
                 }
             ]
         },
@@ -531,7 +693,11 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "unexpectedCurlyAfterCondition",
                     data: { name: "if" },
-                    type: "IfStatement"
+                    type: "IfStatement",
+                    line: 1,
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 19
                 }
             ]
         },
@@ -567,7 +733,11 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "unexpectedCurlyAfterCondition",
                     data: { name: "while" },
-                    type: "WhileStatement"
+                    type: "WhileStatement",
+                    line: 1,
+                    column: 13,
+                    endLine: 1,
+                    endColumn: 22
                 }
             ]
         },
@@ -653,7 +823,9 @@ ruleTester.run("curly", rule, {
                     data: { name: "else" },
                     type: "IfStatement",
                     line: 6,
-                    column: 3
+                    column: 8,
+                    endLine: 11,
+                    endColumn: 2
                 }
             ]
         },
@@ -665,7 +837,11 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "unexpectedCurlyAfter",
                     data: { name: "for-in" },
-                    type: "ForInStatement"
+                    type: "ForInStatement",
+                    line: 1,
+                    column: 22,
+                    endLine: 1,
+                    endColumn: 42
                 }
             ]
         },
@@ -690,7 +866,26 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfterCondition",
                     data: { name: "if" },
-                    type: "IfStatement"
+                    type: "IfStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 7
+                }
+            ]
+        },
+        {
+            code: "if (foo) baz()",
+            output: "if (foo) {baz()}",
+            errors: [
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "if" },
+                    type: "IfStatement",
+                    line: 1,
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 15
                 }
             ]
         },
@@ -742,6 +937,22 @@ ruleTester.run("curly", rule, {
                 }
             ]
         },
+        {
+            code: "do foo(); while (bar)",
+            output: "do {foo();} while (bar)",
+            options: ["all"],
+            errors: [
+                {
+                    messageId: "missingCurlyAfter",
+                    data: { name: "do" },
+                    type: "DoWhileStatement",
+                    line: 1,
+                    column: 4,
+                    endLine: 1,
+                    endColumn: 10
+                }
+            ]
+        },
         {
             code: "do \n foo(); \n while (bar)",
             output: "do \n {foo();} \n while (bar)",
@@ -750,7 +961,27 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfter",
                     data: { name: "do" },
-                    type: "DoWhileStatement"
+                    type: "DoWhileStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 8
+                }
+            ]
+        },
+        {
+            code: "for (var foo in bar) {console.log(foo)}",
+            output: "for (var foo in bar) console.log(foo)",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfter",
+                    data: { name: "for-in" },
+                    type: "ForInStatement",
+                    line: 1,
+                    column: 22,
+                    endLine: 1,
+                    endColumn: 40
                 }
             ]
         },
@@ -787,7 +1018,11 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfter",
                     data: { name: "for-of" },
-                    type: "ForOfStatement"
+                    type: "ForOfStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 2,
+                    endColumn: 18
                 }
             ]
         },
@@ -910,7 +1145,45 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "unexpectedCurlyAfterCondition",
                     data: { name: "for" },
-                    type: "ForStatement"
+                    type: "ForStatement",
+                    line: 1,
+                    column: 27,
+                    endLine: 3,
+                    endColumn: 3
+                }
+            ]
+        },
+        {
+            code: "for (var foo in bar) if (foo) console.log(1); else console.log(2);",
+            output: "for (var foo in bar) {if (foo) console.log(1); else console.log(2);}",
+            options: ["all"],
+            errors: [
+                {
+                    messageId: "missingCurlyAfter",
+                    data: { name: "for-in" },
+                    type: "ForInStatement",
+                    line: 1,
+                    column: 22,
+                    endLine: 1,
+                    endColumn: 67
+                },
+                {
+                    messageId: "missingCurlyAfterCondition",
+                    data: { name: "if" },
+                    type: "IfStatement",
+                    line: 1,
+                    column: 31,
+                    endLine: 1,
+                    endColumn: 46
+                },
+                {
+                    messageId: "missingCurlyAfter",
+                    data: { name: "else" },
+                    type: "IfStatement",
+                    line: 1,
+                    column: 52,
+                    endLine: 1,
+                    endColumn: 67
                 }
             ]
         },
@@ -922,7 +1195,11 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "missingCurlyAfter",
                     data: { name: "for-in" },
-                    type: "ForInStatement"
+                    type: "ForInStatement",
+                    line: 2,
+                    column: 2,
+                    endLine: 3,
+                    endColumn: 22
                 }
             ]
         },
@@ -996,7 +1273,11 @@ ruleTester.run("curly", rule, {
                 {
                     messageId: "unexpectedCurlyAfter",
                     data: { name: "else" },
-                    type: "IfStatement"
+                    type: "IfStatement",
+                    line: 1,
+                    column: 23,
+                    endLine: 1,
+                    endColumn: 33
                 }
             ]
         },
@@ -1041,6 +1322,70 @@ ruleTester.run("curly", rule, {
                 }
             ]
         },
+        {
+            code: "do\n{foo();} while (bar)",
+            output: "do\nfoo(); while (bar)",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfter",
+                    data: { name: "do" },
+                    type: "DoWhileStatement",
+                    line: 2,
+                    column: 1,
+                    endLine: 2,
+                    endColumn: 9
+                }
+            ]
+        },
+        {
+            code: "while (bar) { foo(); }",
+            output: "while (bar)  foo(); ",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfterCondition",
+                    data: { name: "while" },
+                    type: "WhileStatement",
+                    line: 1,
+                    column: 13,
+                    endLine: 1,
+                    endColumn: 23
+                }
+            ]
+        },
+        {
+            code: "while (bar) \n{\n foo(); }",
+            output: "while (bar) \n\n foo(); ",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfterCondition",
+                    data: { name: "while" },
+                    type: "WhileStatement",
+                    line: 2,
+                    column: 1,
+                    endLine: 3,
+                    endColumn: 10
+                }
+            ]
+        },
+        {
+            code: "for (;;) { foo(); }",
+            output: "for (;;)  foo(); ",
+            options: ["multi"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfterCondition",
+                    data: { name: "for" },
+                    type: "ForStatement",
+                    line: 1,
+                    column: 10,
+                    endLine: 1,
+                    endColumn: 20
+                }
+            ]
+        },
         {
             code: "do{[1, 2, 3].map(bar);} while (bar)",
             output: "do[1, 2, 3].map(bar); while (bar)",
@@ -1313,7 +1658,132 @@ ruleTester.run("curly", rule, {
             code: "if (a) { while (cond) if (b) foo() } else bar();",
             output: "if (a) { while (cond) if (b) foo() } else {bar();}",
             options: ["multi", "consistent"],
-            errors: [{ messageId: "missingCurlyAfter", data: { name: "else" }, type: "IfStatement" }]
+            errors: [
+                {
+                    messageId: "missingCurlyAfter",
+                    data: { name: "else" },
+                    type: "IfStatement",
+                    line: 1,
+                    column: 43,
+                    endLine: 1,
+                    endColumn: 49
+                }
+            ]
+        },
+        {
+            code: "if (a)  while (cond) if (b) foo()  \nelse\n {bar();}",
+            output: "if (a)  while (cond) if (b) foo()  \nelse\n bar();",
+            options: ["multi", "consistent"],
+            errors: [
+                {
+                    messageId: "unexpectedCurlyAfter",
+                    data: { name: "else" },
+                    type: "IfStatement",
+                    line: 3,
+                    column: 2,
+                    endLine: 3,
+                    endColumn: 10
+                }
+            ]
+        },
+        {
+            code: "if (a) foo() \nelse\n bar();",
+            output: "if (a) {foo()} \nelse\n {bar();}",
+            errors: [{
+                type: "IfStatement",
+                messageId: "missingCurlyAfterCondition",
+                line: 1,
+                column: 8,
+                endLine: 1,
+                endColumn: 13
+            },
+            {
+                type: "IfStatement",
+                messageId: "missingCurlyAfter",
+                line: 3,
+                column: 2,
+                endLine: 3,
+                endColumn: 8
+            }]
+        },
+        {
+            code: "if (a) { while (cond) if (b) foo() } ",
+            output: "if (a)  while (cond) if (b) foo()  ",
+            options: ["multi", "consistent"],
+            errors: [{
+                messageId: "unexpectedCurlyAfterCondition",
+                data: { name: "if" },
+                type: "IfStatement",
+                line: 1,
+                column: 8,
+                endLine: 1,
+                endColumn: 37
+            }]
+        },
+        {
+            code: "if(a) { if (b) foo(); } if (c) bar(); else if(foo){bar();}",
+            output: "if(a)  if (b) foo();  if (c) bar(); else if(foo)bar();",
+            options: ["multi-or-nest"],
+            errors: [{
+                type: "IfStatement",
+                data: { name: "if" },
+                messageId: "unexpectedCurlyAfterCondition",
+                line: 1,
+                column: 7,
+                endLine: 1,
+                endColumn: 24
+            },
+            {
+                type: "IfStatement",
+                data: { name: "if" },
+                messageId: "unexpectedCurlyAfterCondition",
+                line: 1,
+                column: 51,
+                endLine: 1,
+                endColumn: 59
+            }]
+        },
+        {
+            code: "if (true) [1, 2, 3]\n.bar()",
+            output: "if (true) {[1, 2, 3]\n.bar()}",
+            options: ["multi-line"],
+            errors: [{
+                data: { name: "if" },
+                type: "IfStatement",
+                messageId: "missingCurlyAfterCondition",
+                line: 1,
+                column: 11,
+                endLine: 2,
+                endColumn: 7
+            }]
+        },
+        {
+            code: "for(\n;\n;\n) {foo()}",
+            output: "for(\n;\n;\n) foo()",
+            options: ["multi"],
+            errors: [{
+                data: { name: "for" },
+                type: "ForStatement",
+                messageId: "unexpectedCurlyAfterCondition",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 10
+            }]
+        },
+        {
+            code: "for(\n;\n;\n) \nfoo()\n",
+            output: "for(\n;\n;\n) \n{foo()}\n",
+            options: ["multi-line"],
+            errors: [{
+                data: { name: "for" },
+                type: "ForStatement",
+                messageId: "missingCurlyAfterCondition",
+                line: 5,
+                column: 1,
+                endLine: 5,
+                endColumn: 6
+            }]
         },
         {
 
@@ -1330,6 +1800,46 @@ ruleTester.run("curly", rule, {
                 { messageId: "unexpectedCurlyAfterCondition", data: { name: "if" }, type: "IfStatement" },
                 { messageId: "unexpectedCurlyAfterCondition", data: { name: "while" }, type: "WhileStatement" }
             ]
+        },
+        {
+            code: "for(;;)foo()\n",
+            output: "for(;;){foo()}\n",
+            errors: [{
+                data: { name: "for" },
+                type: "ForStatement",
+                messageId: "missingCurlyAfterCondition",
+                line: 1,
+                column: 8,
+                endLine: 1,
+                endColumn: 13
+            }]
+        },
+        {
+            code: "for(var \ni \n in \n z)foo()\n",
+            output: "for(var \ni \n in \n z){foo()}\n",
+            errors: [{
+                data: { name: "for-in" },
+                type: "ForInStatement",
+                messageId: "missingCurlyAfter",
+                line: 4,
+                column: 4,
+                endLine: 4,
+                endColumn: 9
+            }]
+        },
+        {
+            code: "for(var i of \n z)\nfoo()\n",
+            output: "for(var i of \n z)\n{foo()}\n",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{
+                data: { name: "for-of" },
+                type: "ForOfStatement",
+                messageId: "missingCurlyAfter",
+                line: 3,
+                column: 1,
+                endLine: 3,
+                endColumn: 6
+            }]
         }
     ]
 });
diff --git a/tests/tools/internal-rules/consistent-meta-messages.js b/tests/tools/internal-rules/consistent-meta-messages.js
deleted file mode 100644
index 4df8be92a..000000000
--- a/tests/tools/internal-rules/consistent-meta-messages.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/**
- * @fileoverview Tests for consistent-meta-messages rule.
- * @author 薛定谔的猫<hh_2013@foxmail.com>
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const rule = require("../../../tools/internal-rules/consistent-meta-messages");
-const { RuleTester } = require("../../../lib/rule-tester");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-const ruleTester = new RuleTester();
-
-ruleTester.run("consistent-meta-messages", rule, {
-    valid: [
-        `module.exports = {
-            meta: {
-                messages: {unexpected: "an error occurs."}
-            }
-        };`
-    ],
-    invalid: [
-        {
-            code: `
-            module.exports = {
-                meta: {}
-            };`,
-            errors: [{ messageId: "expectedMessages" }]
-        }
-    ]
-});