Repository: eslint/eslint
Pull request #10108 (2018-03-20 19:26:23)

Docs: remove eslint vs jshint from faq
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[X] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

<!--
    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)**
Remove eslint vs jshint justification from the FAQ. eslint is vastly more popular than jshint these days, I don't think there is a need to justify its existence anymore.

**Is there anything you'd like reviewers to focus on?**
No.




Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 14e9227ee..00194b043 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+v4.19.1 - March 21, 2018
+
+* 3ff5d11 Fix: no-invalid-regexp not understand variable for flags (fixes #10112) (#10113) (薛定谔的猫)
+* abc765c Fix: object-curly-newline minProperties w/default export (fixes #10101) (#10103) (Kevin Partington)
+* 6f9e155 Docs: Update ambiguous for...in example for guard-for-in (#10114) (CJ R)
+* 0360cc2 Chore: Adding debug logs on successful plugin loads (#10100) (Kevin Partington)
+* a717c5d Chore: Adding log at beginning of unit tests in Makefile.js (#10102) (Kevin Partington)
+
 v4.19.0 - March 16, 2018
 
 * 55a1593 Update: consecutive option for one-var (fixes #4680) (#9994) (薛定谔的猫)
diff --git a/README.md b/README.md
index 1f42b791c..3c0299809 100644
--- a/README.md
+++ b/README.md
@@ -186,16 +186,6 @@ According to our policy, any minor update may report more errors than the previo
 
 ## Frequently Asked Questions
 
-### How is ESLint different from JSHint?
-
-The most significant difference is that ESLint has pluggable linting rules. That means you can use the rules it comes with, or you can extend it with rules created by others or by yourself!
-
-### How does ESLint performance compare to JSHint?
-
-ESLint is slower than JSHint, usually 2-3x slower on a single file. This is because ESLint uses Espree to construct an AST before it can evaluate your code whereas JSHint evaluates your code as it's being parsed. The speed is also based on the number of rules you enable; the more rules you enable, the slower the process.
-
-Despite being slower, we believe that ESLint is fast enough to replace JSHint without causing significant pain.
-
 ### I heard ESLint is going to replace JSCS?
 
 Yes. Since we are solving the same problems, ESLint and JSCS teams have decided to join forces and work together in the development of ESLint instead of competing with each other. You can read more about this in both [ESLint](https://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) and [JSCS](https://medium.com/@markelog/jscs-end-of-the-line-bc9bf0b3fdb2#.u76sx334n) announcements.
diff --git a/bin/eslint.js b/bin/eslint.js
index 1a298047a..5e2e91d7a 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -53,7 +53,7 @@ process.once("uncaughtException", err => {
         console.error(err.stack);
     }
 
-    process.exitCode = 1;
+    process.exitCode = 2;
 });
 
 if (useStdIn) {
diff --git a/conf/blank-script.json b/conf/blank-script.json
deleted file mode 100644
index d7d7d37ba..000000000
--- a/conf/blank-script.json
+++ /dev/null
@@ -1,21 +0,0 @@
-{
-  "type": "Program",
-  "body": [],
-  "sourceType": "script",
-  "range": [
-    0,
-    0
-  ],
-  "loc": {
-    "start": {
-      "line": 0,
-      "column": 0
-    },
-    "end": {
-      "line": 0,
-      "column": 0
-    }
-  },
-  "comments": [],
-  "tokens": []
-}
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 4988f2696..2eaeb322a 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -516,8 +516,6 @@ The top-level report object has a `results` array containing all linting results
 
 The top-level report object also has `errorCount` and `warningCount` which give the exact number of errors and warnings respectively on all the files.
 
-Report message objects have a deprecated `source` property. This property **will be removed** from the linting messages returned in `messages` in an upcoming breaking release. If you depend on this property, you should now use the top-level `source` or `output` properties instead.
-
 Once you get a report object, it's up to you to determine how to output the results. Fixes will not be automatically applied to the files, even if you set `fix: true` when constructing the `CLIEngine` instance. To apply fixes to the files, call [`outputFixes`](#cliengineoutputfixes).
 
 ### CLIEngine#resolveFileGlobPatterns()
diff --git a/docs/developer-guide/working-with-custom-formatters.md b/docs/developer-guide/working-with-custom-formatters.md
index 772e15994..2c4da7d32 100644
--- a/docs/developer-guide/working-with-custom-formatters.md
+++ b/docs/developer-guide/working-with-custom-formatters.md
@@ -30,8 +30,7 @@ The output of the previous command will be something like this
                 "message": "Expected { after 'if' condition.",
                 "line": 2,
                 "column": 1,
-                "nodeType": "IfStatement",
-                "source": "if (err) console.log('failed tests: ' + err);"
+                "nodeType": "IfStatement"
             },
             {
                 "ruleId": "no-process-exit",
@@ -39,8 +38,7 @@ The output of the previous command will be something like this
                 "message": "Don't use process.exit(); throw an error instead.",
                 "line": 3,
                 "column": 1,
-                "nodeType": "CallExpression",
-                "source": "process.exit(1);"
+                "nodeType": "CallExpression"
             }
         ],
         "errorCount": 2,
@@ -82,9 +80,6 @@ The following are the fields of the result object:
 - **line**: the line where the issue is located.
 - **column**: the column where the issue is located.
 - **nodeType**: the type of the node in the [AST](https://github.com/estree/estree/blob/master/spec.md#node-objects)
-- **source**: an extract of the code the line where the failure happened.
-
-**Please note**: the `source` property will be removed from the message object in an upcoming breaking release. If you depend on this property, you should now use the `source` or `output` properties from [the result object](#the-result-object) instead.
 
 ## Examples
 
@@ -137,8 +132,7 @@ module.exports = function ( results ) {
                 ruleId: msg.ruleId,
                 message: msg.message,
                 line: msg.line,
-                column: msg.column,
-                source: msg.source
+                column: msg.column
             };
 
             if ( msg.severity === 1 ) {
@@ -207,8 +201,7 @@ module.exports = function ( results ) {
                 ruleId: msg.ruleId,
                 message: msg.message,
                 line: msg.line,
-                column: msg.column,
-                source: msg.source
+                column: msg.column
             };
 
             if ( msg.severity === 1 ) {
diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md
index e0b20bede..84b3e9317 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -213,7 +213,7 @@ All nodes must have `range` property.
 * `range` (`number[]`) is an array of two numbers. Both numbers are a 0-based index which is the position in the array of source code characters. The first is the start position of the node, the second is the end position of the node. `code.slice(node.range[0], node.range[1])` must be the text of the node. This range does not include spaces/parentheses which are around the node.
 * `loc` (`SourceLocation`) must not be `null`. [The `loc` property is defined as nullable by ESTree](https://github.com/estree/estree/blob/25834f7247d44d3156030f8e8a2d07644d771fdb/es5.md#node-objects), but ESLint requires this property. On the other hand, `SourceLocation#source` property can be `undefined`. ESLint does not use the `SourceLocation#source` property.
 
-The `parent` property of all nodes must be rewriteable. ESLint sets each node's parent properties to its parent node while traversing.
+The `parent` property of all nodes must be rewriteable. ESLint sets each node's `parent` property to its parent node while traversing, before any rules have access to the AST.
 
 #### The `Program` node:
 
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index d4a97d8c5..bf51f6c22 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -228,16 +228,12 @@ var RuleTester = require("eslint").RuleTester;
 var ruleTester = new RuleTester();
 ruleTester.run("my-rule", rule, {
     valid: ["bar", "baz"],
-
     invalid: [
         {
             code: "foo",
             errors: [
                 {
-                    messageId: "avoidName",
-                    data: {
-                        name: "foo"
-                    }
+                    messageId: "avoidName"
                 }
             ]
         }
diff --git a/docs/rules/guard-for-in.md b/docs/rules/guard-for-in.md
index ec7860b79..28dc1637f 100644
--- a/docs/rules/guard-for-in.md
+++ b/docs/rules/guard-for-in.md
@@ -33,6 +33,9 @@ for (key in foo) {
     if (Object.prototype.hasOwnProperty.call(foo, key)) {
         doSomething(key);
     }
+}
+
+for (key in foo) {
     if ({}.hasOwnProperty.call(foo, key)) {
         doSomething(key);
     }
diff --git a/docs/rules/no-loop-func.md b/docs/rules/no-loop-func.md
index 6558e7df3..65c753278 100644
--- a/docs/rules/no-loop-func.md
+++ b/docs/rules/no-loop-func.md
@@ -31,6 +31,8 @@ In this case, each function created within the loop returns a different number a
 
 This error is raised to highlight a piece of code that may not work as you expect it to and could also indicate a misunderstanding of how the language works. Your code may run without any problems if you do not fix this error, but in some situations it could behave unexpectedly.
 
+This rule disallows any function within a loop that contains unsafe references (e.g. to modified variables from the outer scope).
+
 Examples of **incorrect** code for this rule:
 
 ```js
@@ -52,11 +54,17 @@ do {
 } while (i);
 
 let foo = 0;
-for (let i=10; i; i--) {
-    // Bad, function is referencing block scoped variable in the outer scope.
-    var a = function() { return foo; };
-    a();
+for (let i = 0; i < 10; ++i) {
+    //Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop
+    setTimeout(() => console.log(foo));
+    foo += 1;
+}
+
+for (let i = 0; i < 10; ++i) {
+    //Bad, `foo` is not in the loop-block's scope and `foo` is modified in/after the loop
+    setTimeout(() => console.log(foo));
 }
+foo = 100;
 ```
 
 Examples of **correct** code for this rule:
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 39a297c79..a4f490e1a 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -450,3 +450,11 @@ ESLint supports `.eslintignore` files to exclude files from the linting process
     **/vendor/*.js
 
 A more detailed breakdown of supported patterns and directories ESLint ignores by default can be found in [Configuring ESLint](configuring.md#ignoring-files-and-directories).
+
+## Exit codes
+
+When linting files, ESLint will exit with one of the following exit codes:
+
+* `0`: Linting was successful and there are no linting errors. If the `--max-warnings` flag is set to `n`, the number of linting warnings is at most `n`.
+* `1`: Linting was successful and there is at least one linting error, or there are more linting warnings than allowed by the `--max-warnings` option.
+* `2`: Linting was unsuccessful due to a configuration problem or an internal error.
diff --git a/lib/api.js b/lib/api.js
index 0a0832a47..91dae3c7c 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -8,9 +8,21 @@
 const Linter = require("./linter");
 
 module.exports = {
-    linter: new Linter(),
     Linter,
     CLIEngine: require("./cli-engine"),
     RuleTester: require("./testers/rule-tester"),
     SourceCode: require("./util/source-code")
 };
+
+let deprecatedLinterInstance = null;
+
+Object.defineProperty(module.exports, "linter", {
+    enumerable: false,
+    get() {
+        if (!deprecatedLinterInstance) {
+            deprecatedLinterInstance = new Linter();
+        }
+
+        return deprecatedLinterInstance;
+    }
+});
diff --git a/lib/cli.js b/lib/cli.js
index 6a5482bf9..faf5dbb35 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -139,7 +139,7 @@ const cli = {
             currentOptions = options.parse(args);
         } catch (error) {
             log.error(error.message);
-            return 1;
+            return 2;
         }
 
         const files = currentOptions._;
@@ -153,11 +153,11 @@ const cli = {
         } else if (currentOptions.printConfig) {
             if (files.length) {
                 log.error("The --print-config option must be used with exactly one file name.");
-                return 1;
+                return 2;
             }
             if (useStdin) {
                 log.error("The --print-config option is not available for piped-in code.");
-                return 1;
+                return 2;
             }
 
             const engine = new CLIEngine(translateOptions(currentOptions));
@@ -176,12 +176,12 @@ const cli = {
 
             if (currentOptions.fix && currentOptions.fixDryRun) {
                 log.error("The --fix option and the --fix-dry-run option cannot be used together.");
-                return 1;
+                return 2;
             }
 
             if (useStdin && currentOptions.fix) {
                 log.error("The --fix option is not available for piped-in code; use --fix-dry-run instead.");
-                return 1;
+                return 2;
             }
 
             const engine = new CLIEngine(translateOptions(currentOptions));
@@ -207,7 +207,7 @@ const cli = {
 
                 return (report.errorCount || tooManyWarnings) ? 1 : 0;
             }
-            return 1;
+            return 2;
 
 
         }
diff --git a/lib/config/plugins.js b/lib/config/plugins.js
index c509c48fe..756f9ff21 100644
--- a/lib/config/plugins.js
+++ b/lib/config/plugins.js
@@ -120,6 +120,26 @@ class Plugins {
                 throw pluginLoadErr;
             }
 
+            // This step is costly, so skip if debug is disabled
+            if (debug.enabled) {
+                const resolvedPath = require.resolve(longName);
+
+                let version = null;
+
+                try {
+                    version = require(`${longName}/package.json`).version;
+                } catch (e) {
+
+                    // Do nothing
+                }
+
+                const loadedPluginAndVersion = version
+                    ? `${longName}@${version}`
+                    : `${longName}, version unknown`;
+
+                debug(`Loaded plugin ${pluginName} (${loadedPluginAndVersion}) (from ${resolvedPath})`);
+            }
+
             this.define(pluginName, plugin);
         }
     }
diff --git a/lib/linter.js b/lib/linter.js
index 8c631522f..aeb558785 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -13,7 +13,6 @@ const eslintScope = require("eslint-scope"),
     evk = require("eslint-visitor-keys"),
     levn = require("levn"),
     lodash = require("lodash"),
-    blankScriptAST = require("../conf/blank-script.json"),
     CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
     ConfigOps = require("./config/config-ops"),
     validator = require("./config/config-validator"),
@@ -138,7 +137,6 @@ function parseJsonConfig(string, location) {
                 ruleId: null,
                 fatal: true,
                 severity: 2,
-                source: null,
                 message: `Failed to parse JSON from '${normalizedString}': ${ex.message}`,
                 line: location.start.line,
                 column: location.start.column + 1
@@ -319,7 +317,6 @@ function getDirectiveComments(filename, ast, ruleMapper) {
                                 problems.push({
                                     ruleId: name,
                                     severity: 2,
-                                    source: null,
                                     message: err.message,
                                     line: comment.loc.start.line,
                                     column: comment.loc.start.column + 1,
@@ -545,7 +542,6 @@ function parse(text, providedParserOptions, parserName, parserMap, filePath) {
                 ruleId: null,
                 fatal: true,
                 severity: 2,
-                source: null,
                 message: ex.message,
                 line: 0,
                 column: 0
@@ -589,7 +585,6 @@ function parse(text, providedParserOptions, parserName, parserMap, filePath) {
 
         // If the message includes a leading line number, strip it:
         const message = `Parsing error: ${ex.message.replace(/^line \d+:/i, "").trim()}`;
-        const source = ex.lineNumber ? SourceCode.splitLines(text)[ex.lineNumber - 1] : null;
 
         return {
             success: false,
@@ -597,7 +592,6 @@ function parse(text, providedParserOptions, parserName, parserMap, filePath) {
                 ruleId: null,
                 fatal: true,
                 severity: 2,
-                source,
                 message,
                 line: ex.lineNumber,
                 column: ex.column
@@ -686,6 +680,22 @@ function createRuleListeners(rule, ruleContext) {
     }
 }
 
+/**
+ * Gets all the ancestors of a given node
+ * @param {ASTNode} node The node
+ * @returns {ASTNode[]} All the ancestor nodes in the AST, not including the provided node, starting
+ * from the root node and going inwards to the parent node.
+ */
+function getAncestors(node) {
+    const ancestorsStartingAtParent = [];
+
+    for (let ancestor = node.parent; ancestor; ancestor = ancestor.parent) {
+        ancestorsStartingAtParent.push(ancestor);
+    }
+
+    return ancestorsStartingAtParent.reverse();
+}
+
 // methods that exist on SourceCode object
 const DEPRECATED_SOURCECODE_PASSTHROUGHS = {
     getSource: "getText",
@@ -737,7 +747,19 @@ const BASE_TRAVERSAL_CONTEXT = Object.freeze(
  */
 function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename) {
     const emitter = createEmitter();
-    const traverser = new Traverser();
+    const nodeQueue = [];
+    let currentNode = sourceCode.ast;
+
+    Traverser.traverse(sourceCode.ast, {
+        enter(node, parent) {
+            node.parent = parent;
+            nodeQueue.push({ isEntering: true, node });
+        },
+        leave(node) {
+            nodeQueue.push({ isEntering: false, node });
+        },
+        visitorKeys: sourceCode.visitorKeys
+    });
 
     /*
      * Create a frozen object with the ruleContext properties and methods that are shared by all rules.
@@ -748,12 +770,12 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
         Object.assign(
             Object.create(BASE_TRAVERSAL_CONTEXT),
             {
-                getAncestors: () => traverser.parents(),
+                getAncestors: () => getAncestors(currentNode),
                 getDeclaredVariables: sourceCode.scopeManager.getDeclaredVariables.bind(sourceCode.scopeManager),
                 getFilename: () => filename,
-                getScope: () => getScope(sourceCode.scopeManager, traverser.current(), parserOptions.ecmaVersion),
+                getScope: () => getScope(sourceCode.scopeManager, currentNode, parserOptions.ecmaVersion),
                 getSourceCode: () => sourceCode,
-                markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, traverser.current(), parserOptions, name),
+                markVariableAsUsed: name => markVariableAsUsed(sourceCode.scopeManager, currentNode, parserOptions, name),
                 parserOptions,
                 parserPath: parserName,
                 parserServices: sourceCode.parserServices,
@@ -853,21 +875,14 @@ function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parser
 
     const eventGenerator = new CodePathAnalyzer(new NodeEventGenerator(emitter));
 
-    /*
-     * Each node has a type property. Whenever a particular type of
-     * node is found, an event is fired. This allows any listeners to
-     * automatically be informed that this type of node has been found
-     * and react accordingly.
-     */
-    traverser.traverse(sourceCode.ast, {
-        enter(node, parent) {
-            node.parent = parent;
-            eventGenerator.enterNode(node);
-        },
-        leave(node) {
-            eventGenerator.leaveNode(node);
-        },
-        visitorKeys: sourceCode.visitorKeys
+    nodeQueue.forEach(traversalInfo => {
+        currentNode = traversalInfo.node;
+
+        if (traversalInfo.isEntering) {
+            eventGenerator.enterNode(currentNode);
+        } else {
+            eventGenerator.leaveNode(currentNode);
+        }
     });
 
     return lintingProblems;
@@ -947,13 +962,6 @@ module.exports = class Linter {
         const settings = config.settings || {};
 
         if (!lastSourceCodes.get(this)) {
-
-            // there's no input, just exit here
-            if (text.trim().length === 0) {
-                lastSourceCodes.set(this, new SourceCode(text, blankScriptAST));
-                return [];
-            }
-
             const parseResult = parse(
                 text,
                 parserOptions,
diff --git a/lib/report-translator.js b/lib/report-translator.js
index 7893a1f7a..17d410d2a 100644
--- a/lib/report-translator.js
+++ b/lib/report-translator.js
@@ -113,15 +113,6 @@ function normalizeReportLoc(descriptor) {
     return descriptor.node.loc;
 }
 
-/**
- * Interpolates data placeholders in report messages
- * @param {MessageDescriptor} descriptor The report message descriptor.
- * @returns {string} The interpolated message for the descriptor
- */
-function normalizeMessagePlaceholders(descriptor) {
-    return interpolate(descriptor.message, descriptor.data);
-}
-
 /**
  * Compares items in a fixes array by range.
  * @param {Fix} a The first message.
@@ -198,9 +189,9 @@ function normalizeFixes(descriptor, sourceCode) {
  * @param {(0|1|2)} options.severity Rule severity
  * @param {(ASTNode|null)} options.node Node
  * @param {string} options.message Error message
+ * @param {string} [options.messageId] The error message ID.
  * @param {{start: SourceLocation, end: (SourceLocation|null)}} options.loc Start and end location
  * @param {{text: string, range: (number[]|null)}} options.fix The fix object
- * @param {string[]} options.sourceLines Source lines
  * @returns {function(...args): ReportInfo} Function that returns information about the report
  */
 function createProblem(options) {
@@ -210,8 +201,7 @@ function createProblem(options) {
         message: options.message,
         line: options.loc.start.line,
         column: options.loc.start.column + 1,
-        nodeType: options.node && options.node.type || null,
-        source: options.sourceLines[options.loc.start.line - 1] || ""
+        nodeType: options.node && options.node.type || null
     };
 
     /*
@@ -255,6 +245,8 @@ module.exports = function createReportTranslator(metadata) {
 
         assertValidNodeInfo(descriptor);
 
+        let computedMessage;
+
         if (descriptor.messageId) {
             if (!metadata.messageIds) {
                 throw new TypeError("context.report() called with a messageId, but no messages were present in the rule metadata.");
@@ -268,7 +260,11 @@ module.exports = function createReportTranslator(metadata) {
             if (!messages || !Object.prototype.hasOwnProperty.call(messages, id)) {
                 throw new TypeError(`context.report() called with a messageId of '${id}' which is not present in the 'messages' config: ${JSON.stringify(messages, null, 2)}`);
             }
-            descriptor.message = messages[id];
+            computedMessage = messages[id];
+        } else if (descriptor.message) {
+            computedMessage = descriptor.message;
+        } else {
+            throw new TypeError("Missing `message` property in report() call; add a message that describes the linting problem.");
         }
 
 
@@ -276,11 +272,10 @@ module.exports = function createReportTranslator(metadata) {
             ruleId: metadata.ruleId,
             severity: metadata.severity,
             node: descriptor.node,
-            message: normalizeMessagePlaceholders(descriptor),
+            message: interpolate(computedMessage, descriptor.data),
             messageId: descriptor.messageId,
             loc: normalizeReportLoc(descriptor),
-            fix: normalizeFixes(descriptor, metadata.sourceCode),
-            sourceLines: metadata.sourceCode.lines
+            fix: normalizeFixes(descriptor, metadata.sourceCode)
         });
     };
 };
diff --git a/lib/rules/no-cond-assign.js b/lib/rules/no-cond-assign.js
index f949bcc88..4c7b5722d 100644
--- a/lib/rules/no-cond-assign.js
+++ b/lib/rules/no-cond-assign.js
@@ -139,7 +139,8 @@ module.exports = {
             DoWhileStatement: testForAssign,
             ForStatement: testForAssign,
             IfStatement: testForAssign,
-            WhileStatement: testForAssign
+            WhileStatement: testForAssign,
+            ConditionalExpression: testForAssign
         };
 
     }
diff --git a/lib/rules/no-invalid-regexp.js b/lib/rules/no-invalid-regexp.js
index e57029c9f..7169e0ca7 100644
--- a/lib/rules/no-invalid-regexp.js
+++ b/lib/rules/no-invalid-regexp.js
@@ -98,20 +98,17 @@ module.exports = {
                     return;
                 }
                 const pattern = node.arguments[0].value;
-                let flags = "";
+                let flags = isString(node.arguments[1]) ? node.arguments[1].value : "";
 
-                if (node.arguments[1]) {
-                    flags = isString(node.arguments[1]) ? node.arguments[1].value : null;
-                    if (allowedFlags) {
-                        flags = flags.replace(allowedFlags, "");
-                    }
+                if (allowedFlags) {
+                    flags = flags.replace(allowedFlags, "");
                 }
 
                 // If flags are unknown, check both are errored or not.
                 const message = validateRegExpFlags(flags) || (
-                    (flags === null)
-                        ? validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
-                        : validateRegExpPattern(pattern, flags.indexOf("u") !== -1)
+                    flags
+                        ? validateRegExpPattern(pattern, flags.indexOf("u") !== -1)
+                        : validateRegExpPattern(pattern, true) && validateRegExpPattern(pattern, false)
                 );
 
                 if (message) {
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index 14b5d06a7..39043a8b9 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -116,7 +116,8 @@ function areLineBreaksRequired(node, options, first, last) {
     } else {
 
         // is ImportDeclaration or ExportNamedDeclaration
-        objectProperties = node.specifiers;
+        objectProperties = node.specifiers
+            .filter(s => s.type === "ImportSpecifier" || s.type === "ExportSpecifier");
     }
 
     return objectProperties.length >= options.minProperties ||
diff --git a/lib/util/apply-disable-directives.js b/lib/util/apply-disable-directives.js
index 815394278..c764a9b70 100644
--- a/lib/util/apply-disable-directives.js
+++ b/lib/util/apply-disable-directives.js
@@ -94,7 +94,6 @@ function applyDirectives(options) {
             line: directive.unprocessedDirective.line,
             column: directive.unprocessedDirective.column,
             severity: 2,
-            source: null,
             nodeType: null
         }));
 
diff --git a/lib/util/source-code.js b/lib/util/source-code.js
index 3375f4483..d34151995 100644
--- a/lib/util/source-code.js
+++ b/lib/util/source-code.js
@@ -387,15 +387,13 @@ class SourceCode extends TokenStore {
      * @public
      */
     getNodeByRangeIndex(index) {
-        let result = null,
-            resultParent = null;
+        let result = null;
 
         Traverser.traverse(this.ast, {
             visitorKeys: this.visitorKeys,
-            enter(node, parent) {
+            enter(node) {
                 if (node.range[0] <= index && index < node.range[1]) {
                     result = node;
-                    resultParent = parent;
                 } else {
                     this.skip();
                 }
@@ -407,7 +405,7 @@ class SourceCode extends TokenStore {
             }
         });
 
-        return result ? Object.assign({ parent: resultParent }, result) : null;
+        return result;
     }
 
     /**
diff --git a/package.json b/package.json
index 8a79e8135..6bb7690c9 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.19.0",
+  "version": "4.19.1",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index de218a875..3650acf91 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -399,8 +399,7 @@ class RuleTester {
             if (!lodash.isEqual(beforeAST, afterAST)) {
 
                 // Not using directly to avoid performance problem in node 6.1.0. See #6111
-                // eslint-disable-next-line no-restricted-properties
-                assert.deepEqual(beforeAST, afterAST, "Rule should not modify AST.");
+                assert.deepStrictEqual(beforeAST, afterAST, "Rule should not modify AST.");
             }
         }
 
@@ -487,36 +486,51 @@ class RuleTester {
 
                         /*
                          * Error object.
-                         * This may have a message, node type, line, and/or
+                         * This may have a message, messageId, data, node type, line, and/or
                          * column.
                          */
-                        if (error.message) {
+                        if (hasOwnProperty(error, "message")) {
+                            assert.ok(!hasOwnProperty(error, "messageId"), "Error should not specify both 'message' and a 'messageId'.");
+                            assert.ok(!hasOwnProperty(error, "data"), "Error should not specify both 'data' and 'message'.");
                             assertMessageMatches(message.message, error.message);
-                        }
-
-                        if (error.messageId) {
-                            const hOP = Object.hasOwnProperty.call.bind(Object.hasOwnProperty);
-
-                            // verify that `error.message` is `undefined`
-                            assert.strictEqual(error.message, void 0, "Error should not specify both a message and a messageId.");
-                            if (!hOP(rule, "meta") || !hOP(rule.meta, "messages")) {
-                                assert.fail("Rule must specify a messages hash in `meta`");
-                            }
-                            if (!hOP(rule.meta.messages, error.messageId)) {
+                        } else if (hasOwnProperty(error, "messageId")) {
+                            assert.ok(
+                                hasOwnProperty(rule, "meta") && hasOwnProperty(rule.meta, "messages"),
+                                "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'."
+                            );
+                            if (!hasOwnProperty(rule.meta.messages, error.messageId)) {
                                 const friendlyIDList = `[${Object.keys(rule.meta.messages).map(key => `'${key}'`).join(", ")}]`;
 
-                                assert.fail(`Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
+                                assert(false, `Invalid messageId '${error.messageId}'. Expected one of ${friendlyIDList}.`);
                             }
-
-                            let expectedMessage = rule.meta.messages[error.messageId];
-
-                            if (error.data) {
-                                expectedMessage = interpolate(expectedMessage, error.data);
+                            assert.strictEqual(
+                                error.messageId,
+                                message.messageId,
+                                `messageId '${message.messageId}' does not match expected messageId '${error.messageId}'.`
+                            );
+                            if (hasOwnProperty(error, "data")) {
+
+                                /*
+                                 *  if data was provided, then directly compare the returned message to a synthetic
+                                 *  interpolated message using the same message ID and data provided in the test.
+                                 *  See https://github.com/eslint/eslint/issues/9890 for context.
+                                 */
+                                const unformattedOriginalMessage = rule.meta.messages[error.messageId];
+                                const rehydratedMessage = interpolate(unformattedOriginalMessage, error.data);
+
+                                assert.strictEqual(
+                                    message.message,
+                                    rehydratedMessage,
+                                    `Hydrated message "${rehydratedMessage}" does not match "${message.message}"`
+                                );
                             }
-
-                            assertMessageMatches(message.message, expectedMessage);
                         }
 
+                        assert.ok(
+                            hasOwnProperty(error, "data") ? hasOwnProperty(error, "messageId") : true,
+                            "Error must specify 'messageId' if 'data' is used."
+                        );
+
                         if (error.type) {
                             assert.strictEqual(message.nodeType, error.type, `Error type should be ${error.type}, found ${message.nodeType}`);
                         }
@@ -554,8 +568,7 @@ class RuleTester {
                 } else {
                     const fixResult = SourceCodeFixer.applyFixes(item.code, messages);
 
-                    // eslint-disable-next-line no-restricted-properties
-                    assert.equal(fixResult.output, item.output, "Output is incorrect.");
+                    assert.strictEqual(fixResult.output, item.output, "Output is incorrect.");
                 }
             }
 
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 7742eb71f..a48ef6745 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -138,7 +138,7 @@ describe("bin/eslint.js", () => {
                     ["--stdin"], { cwd: "/", env: { HOME: "/" } }
                 );
 
-                const exitCodePromise = assertExitCode(child, 1);
+                const exitCodePromise = assertExitCode(child, 2);
                 const stderrPromise = getOutput(child).then(output => {
                     assert.match(
                         output.stderr,
@@ -316,7 +316,7 @@ describe("bin/eslint.js", () => {
     describe("handling crashes", () => {
         it("prints the error message to stderr in the event of a crash", () => {
             const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]);
-            const exitCodeAssertion = assertExitCode(child, 1);
+            const exitCodeAssertion = assertExitCode(child, 2);
             const outputAssertion = getOutput(child).then(output => {
                 const expectedSubstring = "Syntax error in selector";
 
@@ -330,7 +330,7 @@ describe("bin/eslint.js", () => {
         it("prints the error message pointing to line of code", () => {
             const invalidConfig = `${__dirname}/../fixtures/bin/.eslintrc.yml`;
             const child = runESLint(["--no-ignore", invalidConfig]);
-            const exitCodeAssertion = assertExitCode(child, 1);
+            const exitCodeAssertion = assertExitCode(child, 2);
             const outputAssertion = getOutput(child).then(output => {
                 const expectedSubstring = "Error: bad indentation of a mapping entry at line";
 
diff --git a/tests/fixtures/testers/rule-tester/messageId.js b/tests/fixtures/testers/rule-tester/messageId.js
new file mode 100644
index 000000000..d7386e395
--- /dev/null
+++ b/tests/fixtures/testers/rule-tester/messageId.js
@@ -0,0 +1,36 @@
+"use strict";
+module.exports.withMetaWithData = {
+    meta: {
+        messages: {
+            avoidFoo: "Avoid using variables named '{{ name }}'.",
+            unused: "An unused key"
+        }
+    },
+    create(context) {
+        return {
+            Identifier(node) {
+                if (node.name === "foo") {
+                    context.report({
+                        node,
+                        messageId: "avoidFoo",
+                        data: {
+                            name: "foo"
+                        }
+                    });
+                }
+            }
+        };
+    }
+};
+
+module.exports.withMessageOnly = {
+    create(context) {
+        return {
+            Identifier(node) {
+                if (node.name === "foo") {
+                    context.report({ node, message: "Avoid using variables named 'foo'."});
+                }
+            }
+        };
+    }
+};
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index e267ed3b2..9ed25a9ef 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -355,8 +355,7 @@ describe("CLIEngine", () => {
                                 column: 11,
                                 endLine: 1,
                                 endColumn: 14,
-                                nodeType: "Identifier",
-                                source: "var bar = foo"
+                                nodeType: "Identifier"
                             }
                         ],
                         errorCount: 1,
@@ -397,8 +396,7 @@ describe("CLIEngine", () => {
                                 severity: 2,
                                 message: "Parsing error: Unexpected token is",
                                 line: 1,
-                                column: 19,
-                                source: "var bar = foothis is a syntax error."
+                                column: 19
                             }
                         ],
                         errorCount: 1,
@@ -439,8 +437,7 @@ describe("CLIEngine", () => {
                                 severity: 2,
                                 message: "Parsing error: Unexpected token",
                                 line: 1,
-                                column: 10,
-                                source: "var bar ="
+                                column: 10
                             }
                         ],
                         errorCount: 1,
@@ -527,8 +524,7 @@ describe("CLIEngine", () => {
                                 severity: 2,
                                 message: "Parsing error: Unexpected token is",
                                 line: 1,
-                                column: 19,
-                                source: "var bar = foothis is a syntax error."
+                                column: 19
                             }
                         ],
                         errorCount: 1,
@@ -1398,8 +1394,7 @@ describe("CLIEngine", () => {
                                     messageId: "unexpected",
                                     nodeType: "BinaryExpression",
                                     ruleId: "eqeqeq",
-                                    severity: 2,
-                                    source: "if (msg == \"hi\") {"
+                                    severity: 2
                                 }
                             ],
                             errorCount: 1,
@@ -1419,8 +1414,7 @@ describe("CLIEngine", () => {
                                     message: "'foo' is not defined.",
                                     nodeType: "Identifier",
                                     ruleId: "no-undef",
-                                    severity: 2,
-                                    source: "var msg = \"hi\" + foo;"
+                                    severity: 2
                                 }
                             ],
                             errorCount: 1,
@@ -3038,7 +3032,6 @@ describe("CLIEngine", () => {
                                     line: 1,
                                     column: 1,
                                     severity: 2,
-                                    source: null,
                                     nodeType: null
                                 }
                             ],
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index b50615f26..7c8801b72 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -121,7 +121,7 @@ describe("cli", () => {
             const filePath = getFixturePath("files");
             const result = cli.execute(`--blah --another ${filePath}`);
 
-            assert.strictEqual(result, 1);
+            assert.strictEqual(result, 2);
         });
 
     });
@@ -245,7 +245,7 @@ describe("cli", () => {
             const filePath = getFixturePath("passing.js");
             const exit = cli.execute(`-f fakeformatter ${filePath}`);
 
-            assert.strictEqual(exit, 1);
+            assert.strictEqual(exit, 2);
         });
     });
 
@@ -265,14 +265,14 @@ describe("cli", () => {
             const filePath = getFixturePath("passing.js");
             const exit = cli.execute(`-f ${formatterPath} ${filePath}`);
 
-            assert.strictEqual(exit, 1);
+            assert.strictEqual(exit, 2);
         });
     });
 
     describe("when executing a file with a lint error", () => {
         it("should exit with error", () => {
             const filePath = getFixturePath("undef.js");
-            const code = `--no-ignore --config --rule no-undef:2 ${filePath}`;
+            const code = `--no-ignore --rule no-undef:2 ${filePath}`;
 
             const exit = cli.execute(code);
 
@@ -308,7 +308,7 @@ describe("cli", () => {
 
     describe("when executing with version flag", () => {
         it("should print out current version", () => {
-            cli.execute("-v");
+            assert.strictEqual(cli.execute("-v"), 0);
 
             assert.strictEqual(log.info.callCount, 1);
         });
@@ -316,7 +316,7 @@ describe("cli", () => {
 
     describe("when executing with help flag", () => {
         it("should print out help", () => {
-            cli.execute("-h");
+            assert.strictEqual(cli.execute("-h"), 0);
 
             assert.strictEqual(log.info.callCount, 1);
         });
@@ -403,7 +403,7 @@ describe("cli", () => {
             assert.throws(() => {
                 const exit = cli.execute(code);
 
-                assert.strictEqual(exit, 1);
+                assert.strictEqual(exit, 2);
             }, /Error while loading rule 'custom-rule': Cannot read property/);
         });
 
@@ -559,7 +559,7 @@ describe("cli", () => {
 
             const exit = cli.execute(code);
 
-            assert.strictEqual(exit, 1);
+            assert.strictEqual(exit, 2);
             assert.isTrue(log.info.notCalled);
             assert.isTrue(log.error.calledOnce);
         });
@@ -572,7 +572,7 @@ describe("cli", () => {
 
             const exit = cli.execute(code);
 
-            assert.strictEqual(exit, 1);
+            assert.strictEqual(exit, 2);
             assert.isTrue(log.info.notCalled);
             assert.isTrue(log.error.calledOnce);
         });
@@ -611,7 +611,7 @@ describe("cli", () => {
             const filePath = getFixturePath("passing.js");
             const exit = cli.execute(`--no-ignore --parser-options test111 ${filePath}`);
 
-            assert.strictEqual(exit, 1);
+            assert.strictEqual(exit, 2);
         });
 
         it("should exit with no error if parser is valid", () => {
@@ -862,7 +862,7 @@ describe("cli", () => {
 
             const exitCode = localCLI.execute("--fix .", "foo = bar;");
 
-            assert.strictEqual(exitCode, 1);
+            assert.strictEqual(exitCode, 2);
         });
 
     });
@@ -1020,7 +1020,7 @@ describe("cli", () => {
 
             const exitCode = localCLI.execute("--fix --fix-dry-run .", "foo = bar;");
 
-            assert.strictEqual(exitCode, 1);
+            assert.strictEqual(exitCode, 2);
         });
     });
 
@@ -1042,7 +1042,7 @@ describe("cli", () => {
 
             assert.isTrue(log.info.notCalled);
             assert.isTrue(log.error.calledOnce);
-            assert.strictEqual(exitCode, 1);
+            assert.strictEqual(exitCode, 2);
         });
 
         it("should error out when executing on text", () => {
@@ -1050,7 +1050,7 @@ describe("cli", () => {
 
             assert.isTrue(log.info.notCalled);
             assert.isTrue(log.error.calledOnce);
-            assert.strictEqual(exitCode, 1);
+            assert.strictEqual(exitCode, 2);
         });
     });
 
diff --git a/tests/lib/formatters/json.js b/tests/lib/formatters/json.js
index 1c52be5a8..a2d6e1b87 100644
--- a/tests/lib/formatters/json.js
+++ b/tests/lib/formatters/json.js
@@ -24,8 +24,7 @@ describe("formatter:json", () => {
             severity: 2,
             line: 5,
             column: 10,
-            ruleId: "foo",
-            source: "foo"
+            ruleId: "foo"
         }]
     }, {
         filePath: "bar.js",
@@ -34,8 +33,7 @@ describe("formatter:json", () => {
             severity: 1,
             line: 6,
             column: 11,
-            ruleId: "bar",
-            source: "bar"
+            ruleId: "bar"
         }]
     }];
 
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 7c3ac678a..9e9a04dbb 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -118,6 +118,24 @@ describe("Linter", () => {
             linter.verify("foo", { rules: { checker: "error", "no-undef": "error" } });
             assert(spy.notCalled);
         });
+
+        it("has all the `parent` properties on nodes when the rule listeners are created", () => {
+            const spy = sandbox.spy(context => {
+                const ast = context.getSourceCode().ast;
+
+                assert.strictEqual(ast.body[0].parent, ast);
+                assert.strictEqual(ast.body[0].expression.parent, ast.body[0]);
+                assert.strictEqual(ast.body[0].expression.left.parent, ast.body[0].expression);
+                assert.strictEqual(ast.body[0].expression.right.parent, ast.body[0].expression);
+
+                return {};
+            });
+
+            linter.defineRule("checker", spy);
+
+            linter.verify("foo + bar", { rules: { checker: "error" } });
+            assert(spy.calledOnce);
+        });
     });
 
     describe("context.getSourceLines()", () => {
@@ -450,43 +468,6 @@ describe("Linter", () => {
             linter.verify(code, config);
             assert(spy.calledOnce);
         });
-
-        it("should attach the node's parent", () => {
-            const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(context => {
-                const node = context.getNodeByRangeIndex(14);
-
-                assert.property(node, "parent");
-                assert.strictEqual(node.parent.type, "VariableDeclarator");
-                return {};
-            });
-
-            linter.defineRule("checker", spy);
-            linter.verify(code, config);
-            assert(spy.calledOnce);
-        });
-
-        it("should not modify the node when attaching the parent", () => {
-            const config = { rules: { checker: "error" } };
-            const spy = sandbox.spy(context => {
-                const node1 = context.getNodeByRangeIndex(10);
-
-                assert.strictEqual(node1.type, "VariableDeclarator");
-
-                const node2 = context.getNodeByRangeIndex(4);
-
-                assert.strictEqual(node2.type, "Identifier");
-                assert.property(node2, "parent");
-                assert.strictEqual(node2.parent.type, "VariableDeclarator");
-                assert.notProperty(node2.parent, "parent");
-                return {};
-            });
-
-            linter.defineRule("checker", spy);
-            linter.verify(code, config);
-            assert(spy.calledOnce);
-        });
-
     });
 
 
@@ -870,6 +851,20 @@ describe("Linter", () => {
             sinon.assert.calledTwice(spyLiteral);
             sinon.assert.calledOnce(spyBinaryExpression);
         });
+
+        it("should throw an error if a rule reports a problem without a message", () => {
+            linter.defineRule("invalid-report", context => ({
+                Program(node) {
+                    context.report({ node });
+                }
+            }));
+
+            assert.throws(
+                () => linter.verify("foo", { rules: { "invalid-report": "error" } }),
+                TypeError,
+                "Missing `message` property in report() call; add a message that describes the linting problem."
+            );
+        });
     });
 
     describe("when config has shared settings for rules", () => {
@@ -1627,7 +1622,6 @@ describe("Linter", () => {
                         column: 1,
                         endLine: 1,
                         endColumn: 25,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -1646,7 +1640,6 @@ describe("Linter", () => {
                         column: 1,
                         endLine: 1,
                         endColumn: 63,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -2703,7 +2696,6 @@ describe("Linter", () => {
             assert.strictEqual(messages.length, 1);
             assert.strictEqual(messages[0].severity, 2);
             assert.isNull(messages[0].ruleId);
-            assert.strictEqual(messages[0].source, BROKEN_TEST_CODE);
             assert.strictEqual(messages[0].line, 1);
             assert.strictEqual(messages[0].column, 4);
             assert.isTrue(messages[0].fatal);
@@ -2721,7 +2713,6 @@ describe("Linter", () => {
 
             assert.strictEqual(messages.length, 1);
             assert.strictEqual(messages[0].severity, 2);
-            assert.strictEqual(messages[0].source, inValidCode[1]);
             assert.isTrue(messages[0].fatal);
             assert.match(messages[0].message, /^Parsing error:/);
         });
@@ -2773,7 +2764,6 @@ describe("Linter", () => {
                     line: 1,
                     column: 1,
                     severity: 1,
-                    source: "var answer = 6 * 7;",
                     nodeType: null
                 }
             );
@@ -2808,6 +2798,21 @@ describe("Linter", () => {
         });
     });
 
+    describe("when evaluating an empty string", () => {
+        it("runs rules", () => {
+            linter.defineRule("no-programs", context => ({
+                Program(node) {
+                    context.report({ node, message: "No programs allowed." });
+                }
+            }));
+
+            assert.strictEqual(
+                linter.verify("", { rules: { "no-programs": "error" } }).length,
+                1
+            );
+        });
+    });
+
     describe("when evaluating code without comments to environment", () => {
         it("should report a violation when using typed array", () => {
             const code = "var array = new Uint8Array();";
@@ -3085,7 +3090,6 @@ describe("Linter", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -4228,7 +4232,6 @@ describe("Linter", () => {
 
                 assert.strictEqual(messages.length, 1);
                 assert.strictEqual(messages[0].severity, 2);
-                assert.isNull(messages[0].source);
                 assert.strictEqual(messages[0].message, errorPrefix + require(parser).expectedError);
             });
 
diff --git a/tests/lib/report-translator.js b/tests/lib/report-translator.js
index f1e00ed3d..e009e4e82 100644
--- a/tests/lib/report-translator.js
+++ b/tests/lib/report-translator.js
@@ -61,8 +61,7 @@ describe("createReportTranslator", () => {
                     message: "foo",
                     line: 2,
                     column: 1,
-                    nodeType: "ExpressionStatement",
-                    source: "bar"
+                    nodeType: "ExpressionStatement"
                 }
             );
         });
@@ -80,8 +79,7 @@ describe("createReportTranslator", () => {
                     column: 1,
                     endLine: 1,
                     endColumn: 4,
-                    nodeType: "ExpressionStatement",
-                    source: "foo"
+                    nodeType: "ExpressionStatement"
                 }
             );
         });
@@ -105,7 +103,6 @@ describe("createReportTranslator", () => {
                     line: 2,
                     column: 1,
                     nodeType: "ExpressionStatement",
-                    source: "bar",
                     fix: {
                         range: [1, 2],
                         text: "foo"
@@ -131,7 +128,6 @@ describe("createReportTranslator", () => {
                     line: 2,
                     column: 1,
                     nodeType: "ExpressionStatement",
-                    source: "bar",
                     fix: {
                         range: [1, 2],
                         text: "foo"
@@ -168,6 +164,15 @@ describe("createReportTranslator", () => {
                 /^context\.report\(\) called with a messageId of '[^']+' which is not present in the 'messages' config:/
             );
         });
+        it("should throw when no message is provided", () => {
+            const reportDescriptor = { node };
+
+            assert.throws(
+                () => translateReport(reportDescriptor),
+                TypeError,
+                "Missing `message` property in report() call; add a message that describes the linting problem."
+            );
+        });
     });
     describe("combining autofixes", () => {
         it("should merge fixes to one if 'fix' function returns an array of fixes.", () => {
@@ -187,7 +192,6 @@ describe("createReportTranslator", () => {
                     line: 2,
                     column: 1,
                     nodeType: "ExpressionStatement",
-                    source: "bar",
                     fix: {
                         range: [1, 5],
                         text: "fooo\nbar"
@@ -216,7 +220,6 @@ describe("createReportTranslator", () => {
                     line: 2,
                     column: 1,
                     nodeType: "ExpressionStatement",
-                    source: "bar",
                     fix: {
                         range: [1, 5],
                         text: "fooo\nbar"
@@ -242,7 +245,6 @@ describe("createReportTranslator", () => {
                     line: 2,
                     column: 1,
                     nodeType: "ExpressionStatement",
-                    source: "bar",
                     fix: {
                         range: [1, 2],
                         text: "foo"
@@ -268,7 +270,6 @@ describe("createReportTranslator", () => {
                     line: 2,
                     column: 1,
                     nodeType: "ExpressionStatement",
-                    source: "bar",
                     fix: {
                         range: [0, 5],
                         text: "\uFEFFfoo\nx"
@@ -300,7 +301,6 @@ describe("createReportTranslator", () => {
                     endLine: 1,
                     endColumn: 4,
                     nodeType: "ExpressionStatement",
-                    source: "foo",
                     fix: {
                         range: [-1, 5],
                         text: "foo\nx"
@@ -339,7 +339,6 @@ describe("createReportTranslator", () => {
                     line: 42,
                     column: 24,
                     nodeType: "ExpressionStatement",
-                    source: "",
                     fix: {
                         range: [1, 1],
                         text: ""
@@ -360,8 +359,7 @@ describe("createReportTranslator", () => {
                     message: "hello ExpressionStatement",
                     nodeType: "ExpressionStatement",
                     line: 1,
-                    column: 4,
-                    source: "foo"
+                    column: 4
                 }
             );
         });
@@ -375,8 +373,7 @@ describe("createReportTranslator", () => {
                     message: "hello ExpressionStatement",
                     nodeType: "ExpressionStatement",
                     line: 1,
-                    column: 4,
-                    source: "foo"
+                    column: 4
                 }
             );
         });
@@ -462,8 +459,7 @@ describe("createReportTranslator", () => {
                     message: "hello world",
                     nodeType: "ExpressionStatement",
                     line: 42,
-                    column: 14,
-                    source: ""
+                    column: 14
                 }
             );
         });
@@ -477,8 +473,7 @@ describe("createReportTranslator", () => {
                     message: "hello world",
                     nodeType: "ExpressionStatement",
                     line: 42,
-                    column: 14,
-                    source: ""
+                    column: 14
                 }
             );
         });
@@ -494,8 +489,7 @@ describe("createReportTranslator", () => {
                     line: 1,
                     column: 1,
                     endLine: 1,
-                    endColumn: 4,
-                    source: "foo"
+                    endColumn: 4
                 }
             );
         });
@@ -511,8 +505,7 @@ describe("createReportTranslator", () => {
                     line: 1,
                     column: 1,
                     endLine: 1,
-                    endColumn: 4,
-                    source: "foo"
+                    endColumn: 4
                 }
             );
         });
@@ -526,8 +519,7 @@ describe("createReportTranslator", () => {
                     message: "hello world",
                     nodeType: null,
                     line: 1,
-                    column: 1,
-                    source: "foo"
+                    column: 1
                 }
             );
         });
@@ -543,8 +535,7 @@ describe("createReportTranslator", () => {
                     line: 1,
                     column: 1,
                     endLine: 1,
-                    endColumn: 4,
-                    source: "foo"
+                    endColumn: 4
                 }
             );
         });
@@ -563,7 +554,6 @@ describe("createReportTranslator", () => {
                     column: 1,
                     endLine: 1,
                     endColumn: 4,
-                    source: "foo",
                     fix: { range: [1, 1], text: "" }
                 }
             );
@@ -589,8 +579,7 @@ describe("createReportTranslator", () => {
                     message: "hello world",
                     nodeType: null,
                     line: 1,
-                    column: 2,
-                    source: "foo"
+                    column: 2
                 }
             );
         });
@@ -604,8 +593,7 @@ describe("createReportTranslator", () => {
                     message: "hello world",
                     nodeType: null,
                     line: 1,
-                    column: 2,
-                    source: "foo"
+                    column: 2
                 }
             );
         });
diff --git a/tests/lib/rules/no-cond-assign.js b/tests/lib/rules/no-cond-assign.js
index b192635c8..8b8e1ae56 100644
--- a/tests/lib/rules/no-cond-assign.js
+++ b/tests/lib/rules/no-cond-assign.js
@@ -40,7 +40,8 @@ ruleTester.run("no-cond-assign", rule, {
         { code: "if ((node => node = parentNode)(someNode)) { }", options: ["always"], parserOptions: { ecmaVersion: 6 } },
         { code: "if (function(node) { return node = parentNode; }) { }", options: ["except-parens"] },
         { code: "if (function(node) { return node = parentNode; }) { }", options: ["always"] },
-        { code: "x = 0;", options: ["always"] }
+        { code: "x = 0;", options: ["always"] },
+        "var x; var b = (x === 0) ? 1 : 0;"
     ],
     invalid: [
         { code: "var x; if (x = 0) { var b = 1; }", errors: [{ messageId: "missing", type: "IfStatement", line: 1, column: 12 }] },
@@ -59,6 +60,7 @@ ruleTester.run("no-cond-assign", rule, {
         { code: "if ((x = 0)) { }", options: ["always"], errors: [{ messageId: "unexpected", data: { type: "an 'if' statement" }, type: "IfStatement" }] },
         { code: "while ((x = 0)) { }", options: ["always"], errors: [{ messageId: "unexpected", data: { type: "a 'while' statement" }, type: "WhileStatement" }] },
         { code: "do { } while ((x = x + 1));", options: ["always"], errors: [{ messageId: "unexpected", data: { type: "a 'do...while' statement" }, type: "DoWhileStatement" }] },
-        { code: "for(; (x = y); ) { }", options: ["always"], errors: [{ messageId: "unexpected", data: { type: "a 'for' statement" }, type: "ForStatement" }] }
+        { code: "for(; (x = y); ) { }", options: ["always"], errors: [{ messageId: "unexpected", data: { type: "a 'for' statement" }, type: "ForStatement" }] },
+        { code: "var x; var b = (x = 0) ? 1 : 0;", errors: [{ messageId: "missing", type: "ConditionalExpression" }] }
     ]
 });
diff --git a/tests/lib/rules/no-fallthrough.js b/tests/lib/rules/no-fallthrough.js
index c2e48fadf..4d8e4514b 100644
--- a/tests/lib/rules/no-fallthrough.js
+++ b/tests/lib/rules/no-fallthrough.js
@@ -162,8 +162,8 @@ ruleTester.run("no-fallthrough", rule, {
             }],
             errors: [
                 {
-                    message: errorsDefault.message,
-                    type: errorsDefault.type,
+                    message: errorsDefault[0].message,
+                    type: errorsDefault[0].type,
                     line: 4,
                     column: 1
                 }
diff --git a/tests/lib/rules/no-invalid-regexp.js b/tests/lib/rules/no-invalid-regexp.js
index 3b9dac536..305f4e0d3 100644
--- a/tests/lib/rules/no-invalid-regexp.js
+++ b/tests/lib/rules/no-invalid-regexp.js
@@ -23,6 +23,7 @@ ruleTester.run("no-invalid-regexp", rule, {
         "new RegExp",
         "new RegExp('.', 'im')",
         "global.RegExp('\\\\')",
+        "new RegExp('.', y)",
         { code: "new RegExp('.', 'y')", options: [{ allowConstructorFlags: ["y"] }] },
         { code: "new RegExp('.', 'u')", options: [{ allowConstructorFlags: ["U"] }] },
         { code: "new RegExp('.', 'yu')", options: [{ allowConstructorFlags: ["y", "u"] }] },
diff --git a/tests/lib/rules/object-curly-newline.js b/tests/lib/rules/object-curly-newline.js
index 73d9a233a..a3235a91a 100644
--- a/tests/lib/rules/object-curly-newline.js
+++ b/tests/lib/rules/object-curly-newline.js
@@ -493,6 +493,10 @@ ruleTester.run("object-curly-newline", rule, {
             ].join("\n"),
             options: [{ ImportDeclaration: { minProperties: 3 } }]
         },
+        {
+            code: "import DefaultExport, {a} from 'module';",
+            options: [{ ImportDeclaration: { minProperties: 2 } }]
+        },
 
         // "ExportDeclaration" ---------------------------------------------
         {
@@ -1590,6 +1594,19 @@ ruleTester.run("object-curly-newline", rule, {
                 { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
             ]
         },
+        {
+            code: "import DefaultExport, {a, b} from 'module';",
+            output: [
+                "import DefaultExport, {",
+                "a, b",
+                "} from 'module';"
+            ].join("\n"),
+            options: [{ ImportDeclaration: { minProperties: 2 } }],
+            errors: [
+                { line: 1, column: 23, message: "Expected a line break after this opening brace." },
+                { line: 1, column: 28, message: "Expected a line break before this closing brace." }
+            ]
+        },
 
         // "ExportDeclaration" ---------------------------------------------
         {
diff --git a/tests/lib/testers/no-test-runners.js b/tests/lib/testers/no-test-runners.js
index 5ca2de128..34d8a2a09 100644
--- a/tests/lib/testers/no-test-runners.js
+++ b/tests/lib/testers/no-test-runners.js
@@ -26,7 +26,7 @@ try {
                 { code: "var foo = bar;", output: "invalid output", errors: 1 }
             ]
         });
-    }, /Output is incorrect\. \(' foo = bar;' == 'invalid output'\)$/);
+    }, /Output is incorrect\. \(' foo = bar;' === 'invalid output'\)$/);
 } finally {
     it = tmpIt;
     describe = tmpDescribe;
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index d7a4bd51c..6456359a7 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -234,6 +234,34 @@ describe("RuleTester", () => {
         }, /Output is incorrect/);
     });
 
+    it("should use strict equality to compare output", () => {
+        const replaceProgramWith5Rule = {
+            create: context => ({
+                Program(node) {
+                    context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
+                }
+            })
+        };
+
+        assert.doesNotThrow(() => {
+            ruleTester.run("foo", replaceProgramWith5Rule, {
+                valid: [],
+                invalid: [
+                    { code: "var foo = bar;", output: "5", errors: 1 }
+                ]
+            });
+        });
+
+        assert.throws(() => {
+            ruleTester.run("foo", replaceProgramWith5Rule, {
+                valid: [],
+                invalid: [
+                    { code: "var foo = bar;", output: 5, errors: 1 }
+                ]
+            });
+        }, /Output is incorrect/);
+    });
+
     it("should throw an error when the expected output doesn't match and errors is just a number", () => {
 
         assert.throws(() => {
@@ -830,6 +858,81 @@ describe("RuleTester", () => {
         }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
     });
 
+    // Nominal message/messageId use cases
+    it("should assert match if message provided in both test and result.", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ message: "something" }] }]
+            });
+        }, /Avoid using variables named/);
+
+        assert.doesNotThrow(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }]
+            });
+        });
+    });
+
+    it("should assert match between messageId if provided in both test and result.", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }]
+            });
+        }, "messageId 'avoidFoo' does not match expected messageId 'unused'.");
+
+        assert.doesNotThrow(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
+            });
+        });
+    });
+    it("should assert match between resulting message output if messageId and data provided in both test and result", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }]
+            });
+        }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
+    });
+
+    // messageId/message misconfiguration cases
+    it("should throw if user tests for both message and messageId", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }]
+            });
+        }, "Error should not specify both 'message' and a 'messageId'.");
+    });
+    it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
+            });
+        }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
+    });
+    it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }]
+            });
+        }, /Invalid messageId 'useFoo'/);
+    });
+    it("should throw if data provided without messageId.", () => {
+        assert.throws(() => {
+            ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
+                valid: [],
+                invalid: [{ code: "foo", errors: [{ data: "something" }] }]
+            });
+        }, "Error must specify 'messageId' if 'data' is used.");
+    });
+
     describe("naming test cases", () => {
 
         /**
diff --git a/tests/lib/util/apply-disable-directives.js b/tests/lib/util/apply-disable-directives.js
index a449da063..81e58994f 100644
--- a/tests/lib/util/apply-disable-directives.js
+++ b/tests/lib/util/apply-disable-directives.js
@@ -440,7 +440,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -472,7 +471,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -493,7 +491,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     },
                     {
@@ -527,7 +524,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -551,7 +547,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -575,7 +570,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     },
                     {
@@ -584,7 +578,6 @@ describe("apply-disable-directives", () => {
                         line: 2,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -608,7 +601,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -632,7 +624,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -667,7 +658,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -691,7 +681,6 @@ describe("apply-disable-directives", () => {
                         line: 2,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -715,7 +704,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     },
                     {
@@ -744,7 +732,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     },
                     {
@@ -774,7 +761,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     },
                     {
@@ -783,7 +769,7 @@ describe("apply-disable-directives", () => {
                         line: 2,
                         column: 1,
                         severity: 2,
-                        source: null,
+
                         nodeType: null
                     },
                     {
@@ -809,7 +795,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -842,7 +827,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
@@ -877,7 +861,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 1,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     },
                     {
@@ -886,7 +869,6 @@ describe("apply-disable-directives", () => {
                         line: 1,
                         column: 5,
                         severity: 2,
-                        source: null,
                         nodeType: null
                     }
                 ]
diff --git a/tests/lib/util/source-code.js b/tests/lib/util/source-code.js
index 64082b003..9d7adac04 100644
--- a/tests/lib/util/source-code.js
+++ b/tests/lib/util/source-code.js
@@ -16,7 +16,6 @@ const fs = require("fs"),
     leche = require("leche"),
     Linter = require("../../../lib/linter"),
     SourceCode = require("../../../lib/util/source-code"),
-    Traverser = require("../../../lib/util/traverser"),
     astUtils = require("../../../lib/ast-utils");
 
 //------------------------------------------------------------------------------
@@ -1791,44 +1790,6 @@ describe("SourceCode", () => {
             node = sourceCode.getNodeByRangeIndex(-99);
             assert.isNull(node);
         });
-
-        it("should attach the node's parent", () => {
-            const node = sourceCode.getNodeByRangeIndex(14);
-
-            assert.property(node, "parent");
-            assert.strictEqual(node.parent.type, "VariableDeclarator");
-        });
-
-        it("should not modify the node when attaching the parent", () => {
-            let node = sourceCode.getNodeByRangeIndex(10);
-
-            assert.strictEqual(node.type, "VariableDeclarator");
-            node = sourceCode.getNodeByRangeIndex(4);
-            assert.strictEqual(node.type, "Identifier");
-            assert.property(node, "parent");
-            assert.strictEqual(node.parent.type, "VariableDeclarator");
-            assert.notProperty(node.parent, "parent");
-        });
-
-        it("should use visitorKeys", () => {
-            const text = "a + b";
-            const ast = espree.parse(text, DEFAULT_CONFIG);
-
-            // no traverse BinaryExpression#left
-            sourceCode = new SourceCode({
-                text,
-                ast,
-                parserServices: null,
-                scopeManager: null,
-                visitorKeys: Object.assign({}, Traverser.DEFAULT_VISITOR_KEYS, {
-                    BinaryExpression: ["right"]
-                })
-            });
-            const node = sourceCode.getNodeByRangeIndex(0);
-
-            assert.strictEqual(node.type, "BinaryExpression"); // This is Identifier if 'BinaryExpression#left' was traversed.
-        });
-
     });
 
     describe("isSpaceBetweenTokens()", () => {
diff --git a/tests/tools/eslint-fuzzer.js b/tests/tools/eslint-fuzzer.js
index 91f0ddd82..77a4af5a6 100644
--- a/tests/tools/eslint-fuzzer.js
+++ b/tests/tools/eslint-fuzzer.js
@@ -162,7 +162,6 @@ describe("eslint-fuzzer", function() {
                     ruleId: null,
                     fatal: true,
                     severity: 2,
-                    source: INVALID_SYNTAX,
                     message: `Parsing error: ${expectedSyntaxError.message}`,
                     line: expectedSyntaxError.lineNumber,
                     column: expectedSyntaxError.column
@@ -209,7 +208,6 @@ describe("eslint-fuzzer", function() {
                     ruleId: null,
                     fatal: true,
                     severity: 2,
-                    source: INVALID_SYNTAX,
                     message: `Parsing error: ${expectedSyntaxError.message}`,
                     line: expectedSyntaxError.lineNumber,
                     column: expectedSyntaxError.column
diff --git a/tests/tools/internal-rules/no-invalid-meta.js b/tests/tools/internal-rules/no-invalid-meta.js
index 5d675e1e3..1661ecfe0 100644
--- a/tests/tools/internal-rules/no-invalid-meta.js
+++ b/tests/tools/internal-rules/no-invalid-meta.js
@@ -242,24 +242,14 @@ ruleTester.run("no-invalid-meta", rule, {
                 column: 5
             }]
         },
-
-        /*
-         * Rule doesn't export anything: Should warn on the Program node.
-         * See https://github.com/eslint/eslint/issues/9534
-         */
-
-        /*
-         * Should be invalid, but will currently show as valid due to #9534.
-         * FIXME: Uncomment when #9534 is fixed in major release.
-         * {
-         *     code: "",
-         *     errors: [{
-         *         message: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
-         *         line: 1,
-         *         column: 1
-         *     }]
-         * },
-         */
+        {
+            code: "",
+            errors: [{
+                message: "Rule does not export anything. Make sure rule exports an object according to new rule format.",
+                line: 1,
+                column: 1
+            }]
+        },
         {
             code: "foo();",
             errors: [{