Repository: eslint/eslint
Pull request #9295 (2017-09-13 00:13:00)

Chore: Revert "avoid handling Rules instances in config-validator"
<!--
    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] 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 (http://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)**

This reverts commit 7c95d5d0a44b7f4f7f885621deb7007a7faa3a4b to avoid the performance impact of iterating through all ~200 core rules in the `Linter#getRules` method every time Linter#verify is called.

This is most noticeable when using `RuleTester`, since `Linter#verify` is called a lot of times on small code samples, so the added overhead is a significant portion of the total time.

I noticed this when running the `indent` tests. On `master`, it takes about 3.5 seconds to run all 751 `indent` tests, but after reverting this change it takes about 2.3 seconds.

Eventually, it would be nice to reapply this commit, but we will probably need to add a way to get a single defined rule from a `Linter` instance to make it workable, rather than getting a map of hundreds of rules.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular

eslintbot: LGTM

Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 797bee468..96c3e0754 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -67,6 +67,8 @@ The most important method on `Linter` is `verify()`, which initiates linting of
     * **Note**: If you want to lint text and have your configuration be read and processed, use CLIEngine's [`executeOnFiles`](#executeonfiles) or [`executeOnText`](#executeontext) instead.
 * `options` - (optional) Additional options for this run.
     * `filename` - (optional) the filename to associate with the source code.
+    * `preprocess` - (optional) A function that accepts a string containing source text, and returns an array of strings containing blocks of code to lint. Also see: [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins)
+    * `postprocess` - (optional) A function that accepts an array of problem lists (one list of problems for each block of code from `preprocess`), and returns a one-dimensional array of problems containing problems for the original, unprocessed text. Also see: [Processors in Plugins](/docs/developer-guide/working-with-plugins#processors-in-plugins)
     * `allowInlineConfig` - (optional) set to `false` to disable inline comments from changing eslint rules.
 
 If the third argument is a string, it is interpreted as the `filename`.
diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md
index 59f4161d4..5cf63b7ee 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -72,15 +72,45 @@ processors: {
 
             // you need to return a one-dimensional array of the messages you want to keep
             return [Message];
-        }
+        },
+
+        supportsAutofix: true // (optional, defaults to false)
     }
 }
 ```
 
 The `preprocess` method takes the file contents and filename as arguments, and returns an array of strings to lint. The strings will be linted separately but still be registered to the filename. It's up to the plugin to decide if it needs to return just one part, or multiple pieces. For example in the case of processing `.html` files, you might want to return just one item in the array by combining all scripts, but for `.md` file where each JavaScript block might be independent, you can return multiple items.
 
-The `postprocess` method takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input
-array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the location of all errors and aggregate them into a single flat array and return it.
+The `postprocess` method takes a two-dimensional array of arrays of lint messages and the filename. Each item in the input array corresponds to the part that was returned from the `preprocess` method. The `postprocess` method must adjust the locations of all errors to correspond to locations in the original, unprocessed code, and aggregate them into a single flat array and return it.
+
+Reported problems have the following location information:
+
+```typescript
+{
+    line: number,
+    column: number,
+
+    endLine?: number,
+    endColumn?: number
+}
+```
+
+By default, ESLint will not perform autofixes when a processor is used, even when the `--fix` flag is enabled on the command line. To allow ESLint to autofix code when using your processor, you should take the following additional steps:
+
+1. Update the `postprocess` method to additionally transform the `fix` property of reported problems. All autofixable problems will have a `fix` property, which is an object with the following schema:
+
+    ```js
+    {
+        range: [number, number],
+        text: string
+    }
+    ```
+
+    The `range` property contains two indexes in the code, referring to the start and end location of a contiguous section of text that will be replaced. The `text` property refers to the text that will replace the given range.
+
+    In the initial list of problems, the `fix` property will refer refer to a fix in the processed JavaScript. The `postprocess` method should transform the object to refer to a fix in the original, unprocessed file.
+
+2. Add a `supportsAutofix: true` property to the processor.
 
 You can have both rules and processors in a single plugin. You can also have multiple processors in one plugin.
 To support multiple extensions, add each one to the `processors` element and point them to the same object.
diff --git a/docs/rules/id-length.md b/docs/rules/id-length.md
index b63a88b08..a28407fec 100644
--- a/docs/rules/id-length.md
+++ b/docs/rules/id-length.md
@@ -192,7 +192,7 @@ data["y"] = 3;  // excused because of calculated property access
 Examples of **incorrect** code for this rule with the `{ "max": 10 }` option:
 
 ```js
-/*eslint id-length: ["error", { "max": "10" }]*/
+/*eslint id-length: ["error", { "max": 10 }]*/
 /*eslint-env es6*/
 
 var reallyLongVarName = 5;
@@ -210,7 +210,7 @@ try {
 Examples of **correct** code for this rule with the `{ "max": 10 }` option:
 
 ```js
-/*eslint id-length: ["error", { "max": "10" }]*/
+/*eslint id-length: ["error", { "max": 10 }]*/
 /*eslint-env es6*/
 
 var varName = 5;
diff --git a/docs/rules/no-restricted-globals.md b/docs/rules/no-restricted-globals.md
index c9df9d339..e3d612412 100644
--- a/docs/rules/no-restricted-globals.md
+++ b/docs/rules/no-restricted-globals.md
@@ -77,7 +77,7 @@ Examples of **incorrect** code for a sample `"event"` global variable name, alon
 
 ```js
 /*global event*/
-/* eslint no-restricted-globals: [{ name: "error", message: "Use local parameter instead." }] */
+/* eslint no-restricted-globals: ["error", { name: "error", message: "Use local parameter instead." }] */
 
 function onClick() {
     console.log(event);    // Unexpected global variable 'event'. Use local parameter instead.
diff --git a/docs/rules/prefer-arrow-callback.md b/docs/rules/prefer-arrow-callback.md
index 95ef16b3a..c9132122e 100644
--- a/docs/rules/prefer-arrow-callback.md
+++ b/docs/rules/prefer-arrow-callback.md
@@ -76,7 +76,7 @@ By default `{ "allowUnboundThis": true }`, this `boolean` option allows function
 
 When set to `false` this option prohibits the use of function expressions as callbacks or function arguments entirely, without exception.
 
-`{ "allowNamedFunctions": false }` **will** flag the following examples:
+`{ "allowUnboundThis": false }` **will** flag the following examples:
 
 ```js
 /* eslint prefer-arrow-callback: [ "error", { "allowUnboundThis": false } ] */
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 8e8b6206e..fb0152967 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -359,7 +359,7 @@ The resulting configuration file will be created in the current directory.
 This option instructs ESLint to try to fix as many issues as possible. The fixes are made to the actual files themselves and only the remaining unfixed issues are output. Not all problems are fixable using this option, and the option does not work in these situations:
 
 1. This option throws an error when code is piped to ESLint.
-1. This option has no effect on code that uses processors.
+1. This option has no effect on code that uses a processor, unless the processor opts into allowing autofixes.
 
 #### `--debug`
 
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 8ac336c7a..2814d38b7 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -143,10 +143,8 @@ function calculateStatsPerRun(results) {
  */
 function processText(text, configHelper, filename, fix, allowInlineConfig, linter) {
     let filePath,
-        messages,
         fileExtension,
-        processor,
-        fixedResult;
+        processor;
 
     if (filename) {
         filePath = path.resolve(filename);
@@ -170,51 +168,28 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte
         }
     }
 
-    if (processor) {
-        debug("Using processor");
-        const parsedBlocks = processor.preprocess(text, filename);
-        const unprocessedMessages = [];
+    const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix);
 
-        parsedBlocks.forEach(block => {
-            unprocessedMessages.push(linter.verify(block, config, {
-                filename,
-                allowInlineConfig
-            }));
-        });
-
-        // TODO(nzakas): Figure out how fixes might work for processors
-
-        messages = processor.postprocess(unprocessedMessages, filename);
-
-    } else {
-
-        if (fix) {
-            fixedResult = linter.verifyAndFix(text, config, {
-                filename,
-                allowInlineConfig,
-                fix
-            });
-            messages = fixedResult.messages;
-        } else {
-            messages = linter.verify(text, config, {
-                filename,
-                allowInlineConfig
-            });
-        }
-    }
+    const fixedResult = linter.verifyAndFix(text, config, {
+        filename,
+        allowInlineConfig,
+        fix: !!autofixingEnabled && fix,
+        preprocess: processor && (rawText => processor.preprocess(rawText, filename)),
+        postprocess: processor && (problemLists => processor.postprocess(problemLists, filename))
+    });
 
-    const stats = calculateStatsPerFile(messages);
+    const stats = calculateStatsPerFile(fixedResult.messages);
 
     const result = {
         filePath: filename,
-        messages,
+        messages: fixedResult.messages,
         errorCount: stats.errorCount,
         warningCount: stats.warningCount,
         fixableErrorCount: stats.fixableErrorCount,
         fixableWarningCount: stats.fixableWarningCount
     };
 
-    if (fixedResult && fixedResult.fixed) {
+    if (fixedResult.fixed) {
         result.output = fixedResult.output;
     }
 
@@ -419,13 +394,9 @@ class CLIEngine {
             });
         }
 
-        if (this.options.rules && Object.keys(this.options.rules).length) {
-            const loadedRules = this.linter.getRules();
-
-            Object.keys(this.options.rules).filter(ruleId => loadedRules.has(ruleId)).forEach(name => {
-                validator.validateRuleOptions(loadedRules.get(name), name, this.options.rules[name], "CLI");
-            });
-        }
+        Object.keys(this.options.rules || {}).forEach(name => {
+            validator.validateRuleOptions(name, this.options.rules[name], "CLI", this.linter.rules);
+        });
 
         this.config = new Config(this.options, this.linter);
     }
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index e74fd4640..87412dd2a 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -546,7 +546,7 @@ function loadFromDisk(resolvedPath, configContext) {
         }
 
         // validate the configuration before continuing
-        validator.validate(config, resolvedPath.configFullName, configContext.linterContext.getRules(), configContext.linterContext.environments);
+        validator.validate(config, resolvedPath.configFullName, configContext.linterContext.rules, configContext.linterContext.environments);
 
         /*
          * If an `extends` property is defined, it represents a configuration file to use as
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index c06354e77..22bc1efbd 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -14,7 +14,9 @@ const ajv = require("../util/ajv"),
     configSchema = require("../../conf/config-schema.js"),
     util = require("util");
 
-const ruleValidators = new WeakMap();
+const validators = {
+    rules: Object.create(null)
+};
 
 //------------------------------------------------------------------------------
 // Private
@@ -23,11 +25,13 @@ let validateSchema;
 
 /**
  * Gets a complete options schema for a rule.
- * @param {{create: Function, schema: (Array|null)}} rule A new-style rule object
+ * @param {string} id The rule's unique name.
+ * @param {Rules} rulesContext Rule context
  * @returns {Object} JSON Schema for the rule's options.
  */
-function getRuleOptionsSchema(rule) {
-    const schema = rule.schema || rule.meta && rule.meta.schema;
+function getRuleOptionsSchema(id, rulesContext) {
+    const rule = rulesContext.get(id),
+        schema = rule && rule.schema || rule && rule.meta && rule.meta.schema;
 
     // Given a tuple of schemas, insert warning level at the beginning
     if (Array.isArray(schema)) {
@@ -68,20 +72,19 @@ function validateRuleSeverity(options) {
 
 /**
 * Validates the non-severity options passed to a rule, based on its schema.
-* @param {{create: Function}} rule The rule to validate
+* @param {string} id The rule's unique name
 * @param {array} localOptions The options for the rule, excluding severity
+* @param {Rules} rulesContext Rule context
 * @returns {void}
 */
-function validateRuleSchema(rule, localOptions) {
-    if (!ruleValidators.has(rule)) {
-        const schema = getRuleOptionsSchema(rule);
+function validateRuleSchema(id, localOptions, rulesContext) {
+    const schema = getRuleOptionsSchema(id, rulesContext);
 
-        if (schema) {
-            ruleValidators.set(rule, ajv.compile(schema));
-        }
+    if (!validators.rules[id] && schema) {
+        validators.rules[id] = ajv.compile(schema);
     }
 
-    const validateRule = ruleValidators.get(rule);
+    const validateRule = validators.rules[id];
 
     if (validateRule) {
         validateRule(localOptions);
@@ -93,21 +96,21 @@ function validateRuleSchema(rule, localOptions) {
 
 /**
  * Validates a rule's options against its schema.
- * @param {{create: Function}} rule The rule that the config is being validated for
- * @param {string} ruleId The rule's unique name.
+ * @param {string} id The rule's unique name.
  * @param {array|number} options The given options for the rule.
  * @param {string} source The name of the configuration source to report in any errors.
+ * @param {Rules} rulesContext Rule context
  * @returns {void}
  */
-function validateRuleOptions(rule, ruleId, options, source) {
+function validateRuleOptions(id, options, source, rulesContext) {
     try {
         const severity = validateRuleSeverity(options);
 
         if (severity !== 0 && !(typeof severity === "string" && severity.toLowerCase() === "off")) {
-            validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
+            validateRuleSchema(id, Array.isArray(options) ? options.slice(1) : [], rulesContext);
         }
     } catch (err) {
-        throw new Error(`${source}:\n\tConfiguration for rule "${ruleId}" is invalid:\n${err.message}`);
+        throw new Error(`${source}:\n\tConfiguration for rule "${id}" is invalid:\n${err.message}`);
     }
 }
 
@@ -138,16 +141,16 @@ function validateEnvironment(environment, source, envContext) {
  * Validates a rules config object
  * @param {Object} rulesConfig The rules config object to validate.
  * @param {string} source The name of the configuration source to report in any errors.
- * @param {Map<string, {create: Function}>} rulesMap A map from strings to loaded rules
+ * @param {Rules} rulesContext Rule context
  * @returns {void}
  */
-function validateRules(rulesConfig, source, rulesMap) {
+function validateRules(rulesConfig, source, rulesContext) {
     if (!rulesConfig) {
         return;
     }
 
-    Object.keys(rulesConfig).filter(id => rulesMap.has(id)).forEach(id => {
-        validateRuleOptions(rulesMap.get(id), id, rulesConfig[id], source);
+    Object.keys(rulesConfig).forEach(id => {
+        validateRuleOptions(id, rulesConfig[id], source, rulesContext);
     });
 }
 
@@ -218,13 +221,13 @@ function validateConfigSchema(config, source) {
  * Validates an entire config object.
  * @param {Object} config The config object to validate.
  * @param {string} source The name of the configuration source to report in any errors.
- * @param {Map<string, {create: Function}>} ruleMap A map from rule IDs to defined rules
+ * @param {Rules} rulesContext The rules context
  * @param {Environments} envContext The env context
  * @returns {void}
  */
-function validate(config, source, ruleMap, envContext) {
+function validate(config, source, rulesContext, envContext) {
     validateConfigSchema(config, source);
-    validateRules(config.rules, source, ruleMap);
+    validateRules(config.rules, source, rulesContext);
     validateEnvironment(config.env, source, envContext);
 }
 
diff --git a/lib/linter.js b/lib/linter.js
index 1e9a75028..b362a59ab 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -12,6 +12,7 @@
 const EventEmitter = require("events").EventEmitter,
     eslintScope = require("eslint-scope"),
     levn = require("levn"),
+    lodash = require("lodash"),
     blankScriptAST = require("../conf/blank-script.json"),
     defaultConfig = require("../conf/default-config-options.js"),
     CodePathAnalyzer = require("./code-path-analysis/code-path-analyzer"),
@@ -145,17 +146,15 @@ function parseJsonConfig(string, location) {
  */
 function parseListConfig(string) {
     const items = {};
+    const reg = /[\w\-/]+/g;
+    const elementMatches = string.match(reg);
 
-    // Collapse whitespace around ,
-    string = string.replace(/\s*,\s*/g, ",");
+    if (elementMatches) {
+        elementMatches.forEach(name => {
+            items[name] = true;
+        });
+    }
 
-    string.split(/,+/).forEach(name => {
-        name = name.trim();
-        if (!name) {
-            return;
-        }
-        items[name] = true;
-    });
     return items;
 }
 
@@ -276,7 +275,7 @@ function createDisableDirectives(type, loc, value) {
  * @param {string} filename The file being checked.
  * @param {ASTNode} ast The top node of the AST.
  * @param {Object} config The existing configuration data.
- * @param {Map<string, {create: Function}>} ruleMap A map from rule IDs to defined rules
+ * @param {Linter} linterContext Linter context object
  * @returns {{
  *      config: Object,
  *      problems: Problem[],
@@ -289,9 +288,9 @@ function createDisableDirectives(type, loc, value) {
  * }} Modified config object, along with any problems encountered
  * while parsing config comments
  */
-function modifyConfigsFromComments(filename, ast, config, ruleMap) {
+function modifyConfigsFromComments(filename, ast, config, linterContext) {
 
-    const commentConfig = {
+    let commentConfig = {
         exported: {},
         astGlobals: {},
         rules: {},
@@ -339,9 +338,7 @@ function modifyConfigsFromComments(filename, ast, config, ruleMap) {
                             Object.keys(parseResult.config).forEach(name => {
                                 const ruleValue = parseResult.config[name];
 
-                                if (ruleMap.has(name)) {
-                                    validator.validateRuleOptions(ruleMap.get(name), name, ruleValue, `${filename} line ${comment.loc.start.line}`);
-                                }
+                                validator.validateRuleOptions(name, ruleValue, `${filename} line ${comment.loc.start.line}`, linterContext.rules);
                                 commentRules[name] = ruleValue;
                             });
                         } else {
@@ -363,6 +360,14 @@ function modifyConfigsFromComments(filename, ast, config, ruleMap) {
         }
     });
 
+    // apply environment configs
+    Object.keys(commentConfig.env).forEach(name => {
+        const env = linterContext.environments.get(name);
+
+        if (env) {
+            commentConfig = ConfigOps.merge(commentConfig, env);
+        }
+    });
     Object.assign(commentConfig.rules, commentRules);
 
     return {
@@ -721,7 +726,7 @@ module.exports = class Linter {
      */
 
     /**
-     * Verifies the text against the rules specified by the second argument.
+     * Same as linter.verify, except without support for processors.
      * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
      * @param {ESLintConfig} config An ESLintConfig instance to configure everything.
      * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
@@ -731,7 +736,7 @@ module.exports = class Linter {
      *      Useful if you want to validate JS without comments overriding rules.
      * @returns {Object[]} The results as an array of messages or null if no messages.
      */
-    verify(textOrSourceCode, config, filenameOrOptions) {
+    _verifyWithoutProcessors(textOrSourceCode, config, filenameOrOptions) {
         let text,
             parserServices,
             allowInlineConfig,
@@ -802,7 +807,7 @@ module.exports = class Linter {
 
         // parse global comments and modify config
         if (allowInlineConfig !== false) {
-            const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this.getRules());
+            const modifyConfigResult = modifyConfigsFromComments(filename, sourceCode.ast, config, this);
 
             config = modifyConfigResult.config;
             modifyConfigResult.problems.forEach(problem => problems.push(problem));
@@ -965,6 +970,35 @@ module.exports = class Linter {
         });
     }
 
+    /**
+     * Verifies the text against the rules specified by the second argument.
+     * @param {string|SourceCode} textOrSourceCode The text to parse or a SourceCode object.
+     * @param {ESLintConfig} config An ESLintConfig instance to configure everything.
+     * @param {(string|Object)} [filenameOrOptions] The optional filename of the file being checked.
+     *      If this is not set, the filename will default to '<input>' in the rule context. If
+     *      an object, then it has "filename", "saveState", and "allowInlineConfig" properties.
+     * @param {boolean} [saveState] Indicates if the state from the last run should be saved.
+     *      Mostly useful for testing purposes.
+     * @param {boolean} [filenameOrOptions.allowInlineConfig] Allow/disallow inline comments' ability to change config once it is set. Defaults to true if not supplied.
+     *      Useful if you want to validate JS without comments overriding rules.
+     * @param {function(string): string[]} [filenameOrOptions.preprocess] preprocessor for source text. If provided,
+     *      this should accept a string of source text, and return an array of code blocks to lint.
+     * @param {function(Array<Object[]>): Object[]} [filenameOrOptions.postprocess] postprocessor for report messages. If provided,
+     *      this should accept an array of the message lists for each code block returned from the preprocessor,
+     *      apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
+     * @returns {Object[]} The results as an array of messages or null if no messages.
+     */
+    verify(textOrSourceCode, config, filenameOrOptions) {
+        const preprocess = filenameOrOptions && filenameOrOptions.preprocess || (rawText => [rawText]);
+        const postprocess = filenameOrOptions && filenameOrOptions.postprocess || lodash.flatten;
+
+        return postprocess(
+            preprocess(textOrSourceCode).map(
+                textBlock => this._verifyWithoutProcessors(textBlock, config, filenameOrOptions)
+            )
+        );
+    }
+
     /**
      * Gets the SourceCode object representing the parsed source.
      * @returns {SourceCode} The SourceCode object.
@@ -1012,6 +1046,11 @@ module.exports = class Linter {
      * @param {boolean} options.allowInlineConfig Flag indicating if inline comments
      *      should be allowed.
      * @param {boolean|Function} options.fix Determines whether fixes should be applied
+     * @param {Function} options.preprocess preprocessor for source text. If provided, this should
+     *      accept a string of source text, and return an array of code blocks to lint.
+     * @param {Function} options.postprocess postprocessor for report messages. If provided,
+     *      this should accept an array of the message lists for each code block returned from the preprocessor,
+     *      apply a mapping to the messages as appropriate, and return a one-dimensional array of messages
      * @returns {Object} The result of the fix operation as returned from the
      *      SourceCodeFixer.
      */
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 07aa1e830..0f6468a7e 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -950,12 +950,12 @@ module.exports = {
         }
 
         /**
-         * Check whether the given token is the first token of a statement.
+         * Check whether the given token is on the first line of a statement.
          * @param {Token} token The token to check.
          * @param {ASTNode} leafNode The expression node that the token belongs directly.
-         * @returns {boolean} `true` if the token is the first token of a statement.
+         * @returns {boolean} `true` if the token is on the first line of a statement.
          */
-        function isFirstTokenOfStatement(token, leafNode) {
+        function isOnFirstLineOfStatement(token, leafNode) {
             let node = leafNode;
 
             while (node.parent && !node.parent.type.endsWith("Statement") && !node.parent.type.endsWith("Declaration")) {
@@ -963,7 +963,7 @@ module.exports = {
             }
             node = node.parent;
 
-            return !node || node.range[0] === token.range[0];
+            return !node || node.loc.start.line === token.loc.start.line;
         }
 
         const baseOffsetListeners = {
@@ -1076,7 +1076,7 @@ module.exports = {
                 //     /*else*/ qiz ;
                 if (!options.flatTernaryExpressions ||
                     !astUtils.isTokenOnSameLine(node.test, node.consequent) ||
-                    isFirstTokenOfStatement(firstToken, node)
+                    isOnFirstLineOfStatement(firstToken, node)
                 ) {
                     const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
                     const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
diff --git a/lib/rules/newline-per-chained-call.js b/lib/rules/newline-per-chained-call.js
index 613429d13..356c4baf3 100644
--- a/lib/rules/newline-per-chained-call.js
+++ b/lib/rules/newline-per-chained-call.js
@@ -19,7 +19,7 @@ module.exports = {
             category: "Stylistic Issues",
             recommended: false
         },
-
+        fixable: "whitespace",
         schema: [{
             type: "object",
             properties: {
@@ -40,6 +40,18 @@ module.exports = {
 
         const sourceCode = context.getSourceCode();
 
+        /**
+         * Get the prefix of a given MemberExpression node.
+         * If the MemberExpression node is a computed value it returns a
+         * left bracket. If not it returns a period.
+         *
+         * @param  {ASTNode} node - A MemberExpression node to get
+         * @returns {string} The prefix of the node.
+         */
+        function getPrefix(node) {
+            return node.computed ? "[" : ".";
+        }
+
         /**
          * Gets the property text of a given MemberExpression node.
          * If the text is multiline, this returns only the first line.
@@ -48,7 +60,7 @@ module.exports = {
          * @returns {string} The property text of the node.
          */
         function getPropertyText(node) {
-            const prefix = node.computed ? "[" : ".";
+            const prefix = getPrefix(node);
             const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
             const suffix = node.computed && lines.length === 1 ? "]" : "";
 
@@ -70,13 +82,18 @@ module.exports = {
                     parent = parent.callee.object;
                 }
 
-                if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) {
+                if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
                     context.report({
                         node: callee.property,
                         loc: callee.property.loc.start,
                         message: "Expected line break before `{{callee}}`.",
                         data: {
                             callee: getPropertyText(callee)
+                        },
+                        fix(fixer) {
+                            const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
+
+                            return fixer.insertTextBefore(firstTokenAfterObject, "\n");
                         }
                     });
                 }
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 7e350d29e..9c8fe70f0 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -336,6 +336,23 @@ module.exports = {
             }
         }
 
+        /**
+         * Check if a member expression contains a call expression
+         * @param {ASTNode} node MemberExpression node to evaluate
+         * @returns {boolean} true if found, false if not
+         */
+        function doesMemberExpressionContainCallExpression(node) {
+            let currentNode = node.object;
+            let currentNodeType = node.object.type;
+
+            while (currentNodeType === "MemberExpression") {
+                currentNode = currentNode.object;
+                currentNodeType = currentNode.type;
+            }
+
+            return currentNodeType === "CallExpression";
+        }
+
         /**
          * Evaluate a new call
          * @param {ASTNode} node node to evaluate
@@ -343,10 +360,21 @@ module.exports = {
          * @private
          */
         function checkCallNew(node) {
-            if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node)) {
-                const hasNewParensException = node.callee.type === "NewExpression" && !isNewExpressionWithParens(node.callee);
+            const callee = node.callee;
 
-                if (hasDoubleExcessParens(node.callee) || !isIIFE(node) && !hasNewParensException) {
+            if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) {
+                const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);
+
+                if (
+                    hasDoubleExcessParens(callee) ||
+                    !isIIFE(node) && !hasNewParensException && !(
+
+                        // Allow extra parens around a new expression if
+                        // there are intervening parentheses.
+                        callee.type === "MemberExpression" &&
+                        doesMemberExpressionContainCallExpression(callee)
+                    )
+                ) {
                     report(node.callee);
                 }
             }
@@ -574,8 +602,10 @@ module.exports = {
             LogicalExpression: checkBinaryLogical,
 
             MemberExpression(node) {
+                const nodeObjHasExcessParens = hasExcessParens(node.object);
+
                 if (
-                    hasExcessParens(node.object) &&
+                    nodeObjHasExcessParens &&
                     precedence(node.object) >= precedence(node) &&
                     (
                         node.computed ||
@@ -589,6 +619,13 @@ module.exports = {
                 ) {
                     report(node.object);
                 }
+
+                if (nodeObjHasExcessParens &&
+                  node.object.type === "CallExpression" &&
+                  node.parent.type !== "NewExpression") {
+                    report(node.object);
+                }
+
                 if (node.computed && hasExcessParens(node.property)) {
                     report(node.property);
                 }
diff --git a/lib/rules/no-undef-init.js b/lib/rules/no-undef-init.js
index 9df40e9ce..7e58f55a6 100644
--- a/lib/rules/no-undef-init.js
+++ b/lib/rules/no-undef-init.js
@@ -43,6 +43,10 @@ module.exports = {
                         message: "It's not necessary to initialize '{{name}}' to undefined.",
                         data: { name },
                         fix(fixer) {
+                            if (node.parent.kind === "var") {
+                                return null;
+                            }
+
                             if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") {
 
                                 // Don't fix destructuring assignment to `undefined`.
diff --git a/package.json b/package.json
index 898ce38ad..0fed8f35f 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
     "chalk": "^2.1.0",
     "concat-stream": "^1.6.0",
     "cross-spawn": "^5.1.0",
-    "debug": "^2.6.8",
+    "debug": "^3.0.1",
     "doctrine": "^2.0.0",
     "eslint-scope": "^3.7.1",
     "espree": "^3.5.0",
@@ -64,7 +64,7 @@
     "natural-compare": "^1.4.0",
     "optionator": "^0.8.2",
     "path-is-inside": "^1.0.2",
-    "pluralize": "^4.0.0",
+    "pluralize": "^7.0.0",
     "progress": "^2.0.0",
     "require-uncached": "^1.0.3",
     "semver": "^5.3.0",
@@ -89,7 +89,7 @@
     "eslint-plugin-node": "^5.1.0",
     "eslint-release": "^0.10.1",
     "eslump": "1.6.0",
-    "esprima": "^3.1.3",
+    "esprima": "^4.0.0",
     "esprima-fb": "^15001.1001.0-dev-harmony-fb",
     "istanbul": "^0.4.5",
     "jsdoc": "^3.4.3",
@@ -100,7 +100,7 @@
     "karma-phantomjs-launcher": "^1.0.4",
     "leche": "^2.1.2",
     "load-perf": "^0.2.0",
-    "markdownlint": "^0.5.0",
+    "markdownlint": "^0.6.1",
     "mocha": "^3.4.2",
     "mock-fs": "^4.3.0",
     "npm-license": "^0.3.3",
@@ -108,7 +108,7 @@
     "proxyquire": "^1.8.0",
     "shelljs": "^0.7.7",
     "shelljs-nodecli": "~0.1.1",
-    "sinon": "^2.3.2",
+    "sinon": "^3.2.1",
     "temp": "^0.8.3",
     "through": "^2.3.8"
   },


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index 98297262b..d7e14878c 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -319,7 +319,7 @@ class RuleTester {
                 }
             }));
 
-            const schema = validator.getRuleOptionsSchema(rule);
+            const schema = validator.getRuleOptionsSchema(ruleName, linter.rules);
 
             if (schema) {
                 ajv.validateSchema(schema);
@@ -335,7 +335,7 @@ class RuleTester {
                 }
             }
 
-            validator.validate(config, "rule-tester", linter.getRules(), new Environments());
+            validator.validate(config, "rule-tester", linter.rules, new Environments());
 
             /*
              * Setup AST getters.
diff --git a/tests/lib/cli-engine.js b/tests/lib/cli-engine.js
index ea564ca00..b862062c8 100644
--- a/tests/lib/cli-engine.js
+++ b/tests/lib/cli-engine.js
@@ -2478,6 +2478,94 @@ describe("CLIEngine", () => {
                 assert.equal(report.results[0].messages[0].message, "'b' is defined but never used.");
                 assert.equal(report.results[0].messages[0].ruleId, "post-processed");
             });
+
+            describe("autofixing with processors", () => {
+                const HTML_PROCESSOR = Object.freeze({
+                    preprocess(text) {
+                        return [text.replace(/^<script>/, "").replace(/<\/script>$/, "")];
+                    },
+                    postprocess(problemLists) {
+                        return problemLists[0].map(problem => {
+                            if (problem.fix) {
+                                const updatedFix = Object.assign({}, problem.fix, {
+                                    range: problem.fix.range.map(index => index + "<script>".length)
+                                });
+
+                                return Object.assign({}, problem, { fix: updatedFix });
+                            }
+                            return problem;
+                        });
+                    }
+                });
+
+
+                it("should run in autofix mode when using a processor that supports autofixing", () => {
+                    engine = new CLIEngine({
+                        useEslintrc: false,
+                        plugins: ["test-processor"],
+                        rules: {
+                            semi: 2
+                        },
+                        extensions: ["js", "txt"],
+                        ignore: false,
+                        fix: true
+                    });
+
+                    engine.addPlugin("test-processor", {
+                        processors: {
+                            ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+                        }
+                    });
+
+                    const report = engine.executeOnText("<script>foo</script>", "foo.html");
+
+                    assert.strictEqual(report.results[0].messages.length, 0);
+                    assert.strictEqual(report.results[0].output, "<script>foo;</script>");
+                });
+
+                it("should not run in autofix mode when using a processor that does not support autofixing", () => {
+                    engine = new CLIEngine({
+                        useEslintrc: false,
+                        plugins: ["test-processor"],
+                        rules: {
+                            semi: 2
+                        },
+                        extensions: ["js", "txt"],
+                        ignore: false,
+                        fix: true
+                    });
+
+                    engine.addPlugin("test-processor", { processors: { ".html": HTML_PROCESSOR } });
+
+                    const report = engine.executeOnText("<script>foo</script>", "foo.html");
+
+                    assert.strictEqual(report.results[0].messages.length, 1);
+                    assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output"));
+                });
+
+                it("should not run in autofix mode when `fix: true` is not provided, even if the processor supports autofixing", () => {
+                    engine = new CLIEngine({
+                        useEslintrc: false,
+                        plugins: ["test-processor"],
+                        rules: {
+                            semi: 2
+                        },
+                        extensions: ["js", "txt"],
+                        ignore: false
+                    });
+
+                    engine.addPlugin("test-processor", {
+                        processors: {
+                            ".html": Object.assign({ supportsAutofix: true }, HTML_PROCESSOR)
+                        }
+                    });
+
+                    const report = engine.executeOnText("<script>foo</script>", "foo.html");
+
+                    assert.strictEqual(report.results[0].messages.length, 1);
+                    assert.isFalse(Object.prototype.hasOwnProperty.call(report.results[0], "output"));
+                });
+            });
         });
     });
 
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index 467035d97..c6fec97ae 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -98,7 +98,7 @@ describe("Validator", () => {
     describe("validate", () => {
 
         it("should do nothing with an empty config", () => {
-            const fn = validator.validate.bind(null, {}, "tests", linter.getRules(), linter.environments);
+            const fn = validator.validate.bind(null, {}, "tests", linter.rules, linter.environments);
 
             assert.doesNotThrow(fn);
         });
@@ -118,7 +118,7 @@ describe("Validator", () => {
                     rules: {}
                 },
                 "tests",
-                linter.getRules(),
+                linter.rules,
                 linter.environments
             );
 
@@ -132,7 +132,7 @@ describe("Validator", () => {
                     foo: true
                 },
                 "tests",
-                linter.getRules(),
+                linter.rules,
                 linter.environments
             );
 
@@ -141,13 +141,13 @@ describe("Validator", () => {
 
         describe("root", () => {
             it("should throw with a string value", () => {
-                const fn = validator.validate.bind(null, { root: "true" }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { root: "true" }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `\"true\"`).");
             });
 
             it("should throw with a numeric value", () => {
-                const fn = validator.validate.bind(null, { root: 0 }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { root: 0 }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"root\" is the wrong type (expected boolean but got `0`).");
             });
@@ -155,13 +155,13 @@ describe("Validator", () => {
 
         describe("globals", () => {
             it("should throw with a string value", () => {
-                const fn = validator.validate.bind(null, { globals: "jQuery" }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { globals: "jQuery" }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `\"jQuery\"`).");
             });
 
             it("should throw with an array value", () => {
-                const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { globals: ["jQuery"] }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"globals\" is the wrong type (expected object but got `[\"jQuery\"]`).");
             });
@@ -169,7 +169,7 @@ describe("Validator", () => {
 
         describe("parser", () => {
             it("should not throw with a null value", () => {
-                const fn = validator.validate.bind(null, { parser: null }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { parser: null }, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
@@ -178,31 +178,31 @@ describe("Validator", () => {
         describe("env", () => {
 
             it("should throw with an array environment", () => {
-                const fn = validator.validate.bind(null, { env: [] }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { env: [] }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `[]`).");
             });
 
             it("should throw with a primitive environment", () => {
-                const fn = validator.validate.bind(null, { env: 1 }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { env: 1 }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"env\" is the wrong type (expected object but got `1`).");
             });
 
             it("should catch invalid environments", () => {
-                const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { env: { browser: true, invalid: true } }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
             });
 
             it("should catch disabled invalid environments", () => {
-                const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { env: { browser: true, invalid: false } }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Environment key \"invalid\" is unknown\n");
             });
 
             it("should do nothing with an undefined environment", () => {
-                const fn = validator.validate.bind(null, {}, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, {}, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
@@ -211,13 +211,13 @@ describe("Validator", () => {
 
         describe("plugins", () => {
             it("should not throw with an empty array", () => {
-                const fn = validator.validate.bind(null, { plugins: [] }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { plugins: [] }, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with a string", () => {
-                const fn = validator.validate.bind(null, { plugins: "react" }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { plugins: "react" }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"plugins\" is the wrong type (expected array but got `\"react\"`).");
             });
@@ -225,13 +225,13 @@ describe("Validator", () => {
 
         describe("settings", () => {
             it("should not throw with an empty object", () => {
-                const fn = validator.validate.bind(null, { settings: {} }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { settings: {} }, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with an array", () => {
-                const fn = validator.validate.bind(null, { settings: ["foo"] }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { settings: ["foo"] }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"settings\" is the wrong type (expected object but got `[\"foo\"]`).");
             });
@@ -239,19 +239,19 @@ describe("Validator", () => {
 
         describe("extends", () => {
             it("should not throw with an empty array", () => {
-                const fn = validator.validate.bind(null, { extends: [] }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { extends: [] }, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should not throw with a string", () => {
-                const fn = validator.validate.bind(null, { extends: "react" }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { extends: "react" }, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with an object", () => {
-                const fn = validator.validate.bind(null, { extends: {} }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { extends: {} }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"extends\" is the wrong type (expected string/array but got `{}`).");
             });
@@ -259,13 +259,13 @@ describe("Validator", () => {
 
         describe("parserOptions", () => {
             it("should not throw with an empty object", () => {
-                const fn = validator.validate.bind(null, { parserOptions: {} }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { parserOptions: {} }, null, linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw with an array", () => {
-                const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { parserOptions: ["foo"] }, null, linter.rules, linter.environments);
 
                 assert.throws(fn, "Property \"parserOptions\" is the wrong type (expected object but got `[\"foo\"]`).");
             });
@@ -274,67 +274,67 @@ describe("Validator", () => {
         describe("rules", () => {
 
             it("should do nothing with an empty rules object", () => {
-                const fn = validator.validate.bind(null, { rules: {} }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: {} }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config with rules", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": [2, "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is off", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["off", "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["off", "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with an invalid config when severity is off", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": "off" } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": "off" } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with an invalid config when severity is an array with 'off'", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": ["off"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-required-options-rule": ["off"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is warn", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["warn", "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["warn", "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is error", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["error", "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["error", "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is Off", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Off", "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Off", "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is Warn", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Warn", "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Warn", "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should do nothing with a valid config when severity is Error", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Error", "second"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": ["Error", "second"] } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should catch invalid rule options", () => {
-                const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-rule": [3, "third"] } }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
             });
@@ -342,7 +342,7 @@ describe("Validator", () => {
             it("should allow for rules with no options", () => {
                 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
 
-                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": 2 } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": 2 } }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
@@ -350,7 +350,7 @@ describe("Validator", () => {
             it("should not allow options for rules with no options", () => {
                 linter.defineRule("mock-no-options-rule", mockNoOptionsRule);
 
-                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue \"extra\" should NOT have more than 0 items.\n");
             });
@@ -358,43 +358,43 @@ describe("Validator", () => {
 
         describe("overrides", () => {
             it("should not throw with an empty overrides array", () => {
-                const fn = validator.validate.bind(null, { overrides: [] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [] }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should not throw with a valid overrides array", () => {
-                const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: {} }] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", rules: {} }] }, "tests", linter.rules, linter.environments);
 
                 assert.doesNotThrow(fn);
             });
 
             it("should throw if override does not specify files", () => {
-                const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n");
             });
 
             it("should throw if override has an empty files array", () => {
-                const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have less than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
             });
 
             it("should throw if override has nested overrides", () => {
-                const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].overrides\".\n");
             });
 
             it("should throw if override extends", () => {
-                const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].extends\".\n");
             });
 
             it("should throw if override tries to set root", () => {
-                const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.getRules(), linter.environments);
+                const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.rules, linter.environments);
 
                 assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
             });
@@ -405,12 +405,12 @@ describe("Validator", () => {
     describe("getRuleOptionsSchema", () => {
 
         it("should return null for a missing rule", () => {
-            assert.equal(validator.getRuleOptionsSchema(linter.rules.get("non-existent-rule")), null);
+            assert.equal(validator.getRuleOptionsSchema("non-existent-rule", linter.rules), null);
         });
 
         it("should not modify object schema", () => {
             linter.defineRule("mock-object-rule", mockObjectRule);
-            assert.deepEqual(validator.getRuleOptionsSchema(linter.rules.get("mock-object-rule")), {
+            assert.deepEqual(validator.getRuleOptionsSchema("mock-object-rule", linter.rules), {
                 enum: ["first", "second"]
             });
         });
@@ -420,43 +420,43 @@ describe("Validator", () => {
     describe("validateRuleOptions", () => {
 
         it("should throw for incorrect warning level number", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", 3, "tests");
+            const fn = validator.validateRuleOptions.bind(null, "mock-rule", 3, "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
         });
 
         it("should throw for incorrect warning level string", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", "booya", "tests");
+            const fn = validator.validateRuleOptions.bind(null, "mock-rule", "booya", "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '\"booya\"').\n");
         });
 
         it("should throw for invalid-type warning level", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [["error"]], "tests");
+            const fn = validator.validateRuleOptions.bind(null, "mock-rule", [["error"]], "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '[ \"error\" ]').\n");
         });
 
         it("should only check warning level for nonexistent rules", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("non-existent-rule"), "non-existent-rule", [3, "foobar"], "tests");
+            const fn = validator.validateRuleOptions.bind(null, "non-existent-rule", [3, "foobar"], "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"non-existent-rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
         });
 
         it("should only check warning level for plugin rules", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("plugin/rule"), "plugin/rule", 3, "tests");
+            const fn = validator.validateRuleOptions.bind(null, "plugin/rule", 3, "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"plugin/rule\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed '3').\n");
         });
 
         it("should throw for incorrect configuration values", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [2, "frist"], "tests");
+            const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "frist"], "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" should be equal to one of the allowed values.\n");
         });
 
         it("should throw for too many configuration values", () => {
-            const fn = validator.validateRuleOptions.bind(null, linter.rules.get("mock-rule"), "mock-rule", [2, "first", "second"], "tests");
+            const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "first", "second"], "tests", linter.rules);
 
             assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"first,second\" should NOT have more than 1 items.\n");
         });
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 5b65fb06c..bd91c4ff8 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -3590,6 +3590,136 @@ describe("Linter", () => {
         });
     });
 
+    describe("processors", () => {
+        beforeEach(() => {
+
+            // A rule that always reports the AST with a message equal to the source text
+            linter.defineRule("report-original-text", context => ({
+                Program(ast) {
+                    context.report({ node: ast, message: context.getSourceCode().text });
+                }
+            }));
+        });
+
+        describe("preprocessors", () => {
+            it("should apply a preprocessor to the code, and lint each code sample separately", () => {
+                const code = "foo bar baz";
+                const problems = linter.verify(
+                    code,
+                    { rules: { "report-original-text": "error" } },
+                    {
+
+                        // Apply a preprocessor that splits the source text into spaces and lints each word individually
+                        preprocess(input) {
+                            assert.strictEqual(input, code);
+                            assert.strictEqual(arguments.length, 1);
+                            return input.split(" ");
+                        }
+                    }
+                );
+
+                assert.strictEqual(problems.length, 3);
+                assert.deepEqual(problems.map(problem => problem.message), ["foo", "bar", "baz"]);
+            });
+        });
+
+        describe("postprocessors", () => {
+            it("should apply a postprocessor to the reported messages", () => {
+                const code = "foo bar baz";
+
+                const problems = linter.verify(
+                    code,
+                    { rules: { "report-original-text": "error" } },
+                    {
+                        preprocess: input => input.split(" "),
+
+                        /*
+                         * Apply a postprocessor that updates the locations of the reported problems
+                         * to make sure they correspond to the locations in the original text.
+                         */
+                        postprocess(problemLists) {
+                            assert.strictEqual(problemLists.length, 3);
+                            assert.strictEqual(arguments.length, 1);
+
+                            problemLists.forEach(problemList => assert.strictEqual(problemList.length, 1));
+                            return problemLists.reduce(
+                                (combinedList, problemList, index) =>
+                                    combinedList.concat(
+                                        problemList.map(
+                                            problem =>
+                                                Object.assign(
+                                                    {},
+                                                    problem,
+                                                    {
+                                                        message: problem.message.toUpperCase(),
+                                                        column: problem.column + index * 4
+                                                    }
+                                                )
+                                        )
+                                    ),
+                                []
+                            );
+                        }
+                    }
+                );
+
+                assert.strictEqual(problems.length, 3);
+                assert.deepEqual(problems.map(problem => problem.message), ["FOO", "BAR", "BAZ"]);
+                assert.deepEqual(problems.map(problem => problem.column), [1, 5, 9]);
+            });
+
+            it("should use postprocessed problem ranges when applying autofixes", () => {
+                const code = "foo bar baz";
+
+                linter.defineRule("capitalize-identifiers", context => ({
+                    Identifier(node) {
+                        if (node.name !== node.name.toUpperCase()) {
+                            context.report({
+                                node,
+                                message: "Capitalize this identifier",
+                                fix: fixer => fixer.replaceText(node, node.name.toUpperCase())
+                            });
+                        }
+                    }
+                }));
+
+                const fixResult = linter.verifyAndFix(
+                    code,
+                    { rules: { "capitalize-identifiers": "error" } },
+                    {
+
+                        /*
+                         * Apply a postprocessor that updates the locations of autofixes
+                         * to make sure they correspond to locations in the original text.
+                         */
+                        preprocess: input => input.split(" "),
+                        postprocess(problemLists) {
+                            return problemLists.reduce(
+                                (combinedProblems, problemList, blockIndex) =>
+                                    combinedProblems.concat(
+                                        problemList.map(problem =>
+                                            Object.assign(problem, {
+                                                fix: {
+                                                    text: problem.fix.text,
+                                                    range: problem.fix.range.map(
+                                                        rangeIndex => rangeIndex + blockIndex * 4
+                                                    )
+                                                }
+                                            }))
+                                    ),
+                                []
+                            );
+                        }
+                    }
+                );
+
+                assert.strictEqual(fixResult.fixed, true);
+                assert.strictEqual(fixResult.messages.length, 0);
+                assert.strictEqual(fixResult.output, "FOO BAR BAZ");
+            });
+        });
+    });
+
     describe("verifyAndFix", () => {
         it("Fixes the code", () => {
             const messages = linter.verifyAndFix("var a", {
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 1339c16f6..006d7f1dd 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -3591,6 +3591,31 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { flatTernaryExpressions: true }]
         },
+        {
+            code: unIndent`
+                function foo() {
+                    return foo ? bar :
+                        baz
+                }
+            `,
+            options: [4, { flatTernaryExpressions: true }]
+        },
+        {
+            code: unIndent`
+                throw foo ? bar :
+                    baz
+            `,
+            options: [4, { flatTernaryExpressions: true }]
+        },
+        {
+            code: unIndent`
+                foo(
+                    bar
+                ) ? baz :
+                    qux
+            `,
+            options: [4, { flatTernaryExpressions: true }]
+        },
         unIndent`
                 foo
                     [
diff --git a/tests/lib/rules/newline-per-chained-call.js b/tests/lib/rules/newline-per-chained-call.js
index 232aafed6..ada18a70b 100644
--- a/tests/lib/rules/newline-per-chained-call.js
+++ b/tests/lib/rules/newline-per-chained-call.js
@@ -28,6 +28,7 @@ ruleTester.run("newline-per-chained-call", rule, {
     }],
     invalid: [{
         code: "_\n.chain({}).map(foo).filter(bar).value();",
+        output: "_\n.chain({}).map(foo)\n.filter(bar)\n.value();",
         errors: [{
             message: "Expected line break before `.filter`."
         }, {
@@ -35,26 +36,31 @@ ruleTester.run("newline-per-chained-call", rule, {
         }]
     }, {
         code: "_\n.chain({})\n.map(foo)\n.filter(bar).value();",
+        output: "_\n.chain({})\n.map(foo)\n.filter(bar)\n.value();",
         errors: [{
             message: "Expected line break before `.value`."
         }]
     }, {
         code: "a().b().c().e.d()",
+        output: "a().b()\n.c().e.d()",
         errors: [{
             message: "Expected line break before `.c`."
         }]
     }, {
         code: "a.b.c().e().d()",
+        output: "a.b.c().e()\n.d()",
         errors: [{
             message: "Expected line break before `.d`."
         }]
     }, {
         code: "_.chain({}).map(a).value(); ",
+        output: "_.chain({}).map(a)\n.value(); ",
         errors: [{
             message: "Expected line break before `.value`."
         }]
     }, {
         code: "var a = m1.m2();\n var b = m1.m2().m3().m4().m5();",
+        output: "var a = m1.m2();\n var b = m1.m2().m3()\n.m4()\n.m5();",
         errors: [{
             message: "Expected line break before `.m4`."
         }, {
@@ -62,11 +68,13 @@ ruleTester.run("newline-per-chained-call", rule, {
         }]
     }, {
         code: "var a = m1.m2();\n var b = m1.m2().m3()\n.m4().m5();",
+        output: "var a = m1.m2();\n var b = m1.m2().m3()\n.m4()\n.m5();",
         errors: [{
             message: "Expected line break before `.m5`."
         }]
     }, {
         code: "var a = m1().m2\n.m3().m4().m5().m6().m7();",
+        output: "var a = m1().m2\n.m3().m4().m5()\n.m6()\n.m7();",
         options: [{
             ignoreChainWithDepth: 3
         }],
@@ -105,6 +113,37 @@ ruleTester.run("newline-per-chained-call", rule, {
             "    // Do something with error.",
             "}).end();"
         ].join("\n"),
+        output: [
+            "http.request({",
+            "    // Param",
+            "    // Param",
+            "    // Param",
+            "}).on('response', function(response) {",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "    // Do something with response.",
+            "})",
+            ".on('error', function(error) {",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "    // Do something with error.",
+            "})",
+            ".end();"
+        ].join("\n"),
         errors: [{
             message: "Expected line break before `.on`."
         }, {
@@ -116,6 +155,13 @@ ruleTester.run("newline-per-chained-call", rule, {
             "    'method3' :",
             "    'method4']()"
         ].join("\n"),
+        output: [
+            "anObject.method1().method2()",
+            "['method' + n]()",
+            "[aCondition ?",
+            "    'method3' :",
+            "    'method4']()"
+        ].join("\n"),
         errors: [{
             message: "Expected line break before `['method' + n]`."
         }, {
@@ -123,8 +169,28 @@ ruleTester.run("newline-per-chained-call", rule, {
         }]
     }, {
         code: "foo.bar()['foo' + \u2029 + 'bar']()",
+        output: "foo.bar()\n['foo' + \u2029 + 'bar']()",
         options: [{ ignoreChainWithDepth: 1 }],
         errors: [{ message: "Expected line break before `['foo' + `." }]
+    }, {
+        code: "foo.bar()[(biz)]()",
+        output: "foo.bar()\n[(biz)]()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `[biz]`." }]
+    }, {
+        code: "(foo).bar().biz()",
+        output: "(foo).bar()\n.biz()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `.biz`." }]
+    }, {
+        code: "foo.bar(). /* comment */ biz()",
+        output: "foo.bar()\n. /* comment */ biz()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `.biz`." }]
+    }, {
+        code: "foo.bar() /* comment */ .biz()",
+        output: "foo.bar() /* comment */ \n.biz()",
+        options: [{ ignoreChainWithDepth: 1 }],
+        errors: [{ message: "Expected line break before `.biz`." }]
     }]
-
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 4958fb6ac..7753c7d66 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -89,6 +89,12 @@ ruleTester.run("no-extra-parens", rule, {
         "a(b = c, (d, e))",
         "(++a)(b); (c++)(d);",
         "new (A())",
+        "new (foo.Baz().foo)",
+        "new (foo.baz.bar().foo.baz)",
+        "new ({}.baz.bar.foo().baz)",
+        "new (doSomething().baz.bar().foo)",
+        "new ([][0].baz.foo().bar.foo)",
+        "new (foo\n.baz\n.bar()\n.foo.baz)",
         "new A()()",
         "(new A)()",
         "(new (Foo || Bar))()",
@@ -516,11 +522,16 @@ ruleTester.run("no-extra-parens", rule, {
         invalid("new (\nfunction(){}\n)", "new \nfunction(){}\n", "FunctionExpression", 1),
         invalid("((function foo() {return 1;}))()", "(function foo() {return 1;})()", "FunctionExpression"),
         invalid("((function(){ return bar(); })())", "(function(){ return bar(); })()", "CallExpression"),
+        invalid("(foo()).bar", "foo().bar", "CallExpression"),
+        invalid("(foo.bar()).baz", "foo.bar().baz", "CallExpression"),
+        invalid("(foo\n.bar())\n.baz", "foo\n.bar()\n.baz", "CallExpression"),
 
         invalid("new (A)", "new A", "Identifier"),
         invalid("(new A())()", "new A()()", "NewExpression"),
         invalid("(new A(1))()", "new A(1)()", "NewExpression"),
         invalid("((new A))()", "(new A)()", "NewExpression"),
+        invalid("new (foo\n.baz\n.bar\n.foo.baz)", "new foo\n.baz\n.bar\n.foo.baz", "MemberExpression"),
+        invalid("new (foo.baz.bar.baz)", "new foo.baz.bar.baz", "MemberExpression"),
 
         invalid("0, (_ => 0)", "0, _ => 0", "ArrowFunctionExpression", 1),
         invalid("(_ => 0), 0", "_ => 0, 0", "ArrowFunctionExpression", 1),
diff --git a/tests/lib/rules/no-undef-init.js b/tests/lib/rules/no-undef-init.js
index 2d44e1656..dfd45e008 100644
--- a/tests/lib/rules/no-undef-init.js
+++ b/tests/lib/rules/no-undef-init.js
@@ -27,17 +27,17 @@ ruleTester.run("no-undef-init", rule, {
     invalid: [
         {
             code: "var a = undefined;",
-            output: "var a;",
+            output: null,
             errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         },
         {
             code: "var a = undefined, b = 1;",
-            output: "var a, b = 1;",
+            output: null,
             errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         },
         {
             code: "var a = 1, b = undefined, c = 5;",
-            output: "var a = 1, b, c = 5;",
+            output: null,
             errors: [{ message: "It's not necessary to initialize 'b' to undefined.", type: "VariableDeclarator" }]
         },
         {
@@ -51,6 +51,47 @@ ruleTester.run("no-undef-init", rule, {
             output: null,
             parserOptions: { ecmaVersion: 6 },
             errors: [{ message: "It's not necessary to initialize '{a}' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "for(var i in [1,2,3]){var a = undefined; for(var j in [1,2,3]){}}",
+            output: null,
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined;",
+            output: "let a;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = undefined, b = 1;",
+            output: "let a, b = 1;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let a = 1, b = undefined, c = 5;",
+            output: "let a = 1, b, c = 5;",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'b' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let [a] = undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize '[a]' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "let {a} = undefined;",
+            output: null,
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize '{a}' to undefined.", type: "VariableDeclarator" }]
+        },
+        {
+            code: "for(var i in [1,2,3]){let a = undefined; for(var j in [1,2,3]){}}",
+            output: "for(var i in [1,2,3]){let a; for(var j in [1,2,3]){}}",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [{ message: "It's not necessary to initialize 'a' to undefined.", type: "VariableDeclarator" }]
         }
     ]
 });