Repository: eslint/eslint (js)

Issue:
[v4] indent: False positive with multi-line parenthesized function calls
**Tell us about your environment**

* **ESLint Version:** 4.1.0
* **Node Version:** 7.10.0
* **npm Version:** 4.2.0

**What parser (default, Babel-ESLint, etc.) are you using?**

default

**Please show your full configuration:**

```json
{
  "root": true,
  "parserOptions": {
    "ecmaVersion": 2017
  },
  "rules": {
    "indent": ["error", 2, {
      "SwitchCase": 1,
      "MemberExpression": 1,
      "FunctionDeclaration": {"parameters": 1},
      "FunctionExpression": {"parameters": 1},
      "CallExpression": {"arguments": 1}
    }]
  }
}
```

**What did you do? Please include the actual source code causing the issue.**

<!-- Paste the source code below: -->
```js
const str = (obj =>
  Object.keys(obj).join('')
)({
  k: 'v',
});
```

**What did you expect to happen?**

No errors

**What actually happened? Please include the actual, raw output from ESLint.**

```
4:1  error  Expected indentation of 4 spaces but found 2  indent
5:1  error  Expected indentation of 2 spaces but found 0  indent
```

The problem seems like it's being caused by the indentation of the arrow function.
If I use a normal function:

```js
const str = (function(obj) {
  return Object.keys(obj).join('')
})({
  k: 'v',
});
```

there are no errors.

Pull request description:
Update: fix parenthesized CallExpression indentation (fixes #8790)
<!--
    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] Bug fix (https://github.com/eslint/eslint/issues/8790)

<!--
    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 fixes a bug in the `indent` rule where the arguments of a call expression were aligned offset the last token of the callee, even when the callee was parenthesized. Instead, the rule should offset from the closing paren in those cases.

**Is there anything you'd like reviewers to focus on?**

Nothing in particular

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e41be9d15..5e017bec8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+v4.1.1 - June 25, 2017
+
+* f307aa0 Fix: ensure configs from a plugin are cached separately (fixes #8792) (#8798) (Teddy Katz)
+* 8b48ae8 Docs: Add doc on parser services (fixes #8390) (#8795) (Victor Hom)
+* 0d041e7 Fix: avoid crashing when using baseConfig with extends (fixes #8791) (#8797) (Teddy Katz)
+* 03213bb Chore: improve comment explanation of `indent` internal functions (#8800) (Teddy Katz)
+* d2e88ed Chore: Fix misleading comment in ConfigCache.js (#8799) (Teddy Katz)
+
 v4.1.0 - June 23, 2017
 
 * e8f1362 Docs: Remove wrong descriptions in `padded-block` rule (#8783) (Plusb Preco)
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 0d8d19e44..1a2e2f8e7 100755
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -48,6 +48,7 @@ module.exports = {
         "func-names": "off",
         "func-style": "off",
         "generator-star-spacing": "off",
+        "getter-return": "off",
         "global-require": "off",
         "guard-for-in": "off",
         "handle-callback-err": "off",
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index f48670896..0f006d781 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -159,7 +159,7 @@ var messages = linter.verifyAndFix("var foo", {
     rules: {
         semi: 2
     }
-}, { filename: "foo.js" });
+});
 ```
 
 Output object from this method:
diff --git a/docs/developer-guide/working-with-plugins.md b/docs/developer-guide/working-with-plugins.md
index b8667d33a..0c8bfad22 100644
--- a/docs/developer-guide/working-with-plugins.md
+++ b/docs/developer-guide/working-with-plugins.md
@@ -218,3 +218,32 @@ Add these keywords into your `package.json` file to make it easy for others to f
 ## Further Reading
 
 * [npm Developer Guide](https://docs.npmjs.com/misc/developers)
+
+### Working with Custom Parsers
+
+If you want to use your own parser and provide additional capabilities for your rules, you can specify your own custom parser. If a `parseForESLint` method is exposed on the parser, this method will be used to parse the code. Otherwise, the `parse` method will be used. Both methods should take in the the source code as the first argument, and an optional configuration object as the second argument (provided as `parserOptions` in a config file). The `parse` method should simply return the AST. The `parseForESLint` method should return an object that contains the required property `ast` and an optional `services` property. `ast` should contain the AST. The `services` property can contain any parser-dependent services (such as type checkers for nodes). The value of the `services` property is available to rules as `context.parserServices`.
+
+You can find an ESLint parser project [here](https://github.com/eslint/typescript-eslint-parser).
+
+    {
+
+        "parser": './path/to/awesome-custom-parser.js'
+    }
+
+```javascript
+var espree = require("espree");
+// awesome-custom-parser.js
+exports.parseForESLint = function(code, options) {
+    return {
+        ast: espree.parse(code, options),
+        services: {
+            foo: function() {
+                console.log("foo");
+            }
+        }
+    };
+};
+
+```
+
+
diff --git a/docs/rules/getter-return.md b/docs/rules/getter-return.md
new file mode 100644
index 000000000..57f92065d
--- /dev/null
+++ b/docs/rules/getter-return.md
@@ -0,0 +1,97 @@
+# Enforces that a return statement is present in property getters (getter-return)
+
+The get syntax binds an object property to a function that will be called when that property is looked up. It was first introduced in ECMAScript 5:
+
+```js
+    var p = {
+        get name(){
+            return "nicholas";
+        }
+    };
+
+    Object.defineProperty(p, "age", {
+        get: function (){
+            return 17;
+        }
+    });
+```
+
+Note that every `getter` is expected to return a value.
+
+## Rule Details
+
+This rule enforces that a return statement is present in property getters.
+
+Examples of **incorrect** code for this rule:
+
+```js
+/*eslint getter-return: "error"*/
+
+p = {
+    get name(){
+        // no returns.
+    }
+};
+
+Object.defineProperty(p, "age", {
+    get: function (){
+        // no returns.
+    }
+});
+
+class P{
+    get name(){
+        // no returns.
+    }
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/*eslint getter-return: "error"*/
+
+p = {
+    get name(){
+        return "nicholas";
+    }
+};
+
+Object.defineProperty(p, "age", {
+    get: function (){
+        return 18;
+    }
+});
+
+class P{
+    get name(){
+        return "nicholas";
+    }
+}
+```
+
+## Options
+
+This rule has an object option:
+
+* `"allowImplicit": false` (default) disallows implicitly returning undefined with a return; statement.
+
+Examples of **correct** code for the `{ "allowImplicit": true }` option:
+
+```js
+/*eslint getter-return: ["error", { allowImplicit: true }]*/
+p = {
+    get name(){
+        return; // return undefined implicitly.
+    }
+};
+```
+
+## When Not To Use It
+
+If your project will not be using ES5 property getters you do not need this rule.
+
+## Further Reading
+
+* [MDN: Functions getter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get)
+* [Understanding ES6: Accessor Properties](https://leanpub.com/understandinges6/read/#leanpub-auto-accessor-properties)
diff --git a/lib/config.js b/lib/config.js
index c69d120ef..2db87d942 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -66,13 +66,14 @@ class Config {
         this.parser = options.parser;
         this.parserOptions = options.parserOptions || {};
 
+        this.configCache = new ConfigCache();
+
         this.baseConfig = options.baseConfig
             ? ConfigOps.merge({}, ConfigFile.loadObject(options.baseConfig, this))
             : { rules: {} };
         this.baseConfig.filePath = "";
         this.baseConfig.baseDirectory = this.options.cwd;
 
-        this.configCache = new ConfigCache();
         this.configCache.setConfig(this.baseConfig.filePath, this.baseConfig);
         this.configCache.setMergedVectorConfig(this.baseConfig.filePath, this.baseConfig);
 
diff --git a/lib/config/config-cache.js b/lib/config/config-cache.js
index b89f33430..07436a87c 100644
--- a/lib/config/config-cache.js
+++ b/lib/config/config-cache.js
@@ -29,7 +29,7 @@ function hash(vector) {
 module.exports = class ConfigCache {
 
     constructor() {
-        this.filePathCache = new Map();
+        this.configFullNameCache = new Map();
         this.localHierarchyCache = new Map();
         this.mergedVectorCache = new Map();
         this.mergedCache = new Map();
@@ -37,23 +37,25 @@ module.exports = class ConfigCache {
 
     /**
      * Gets a config object from the cache for the specified config file path.
-     * @param {string} configFilePath the absolute path to the config file
+     * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
+     * or the absolute path to a config file. This should uniquely identify a config.
      * @returns {Object|null} config object, if found in the cache, otherwise null
      * @private
      */
-    getConfig(configFilePath) {
-        return this.filePathCache.get(configFilePath);
+    getConfig(configFullName) {
+        return this.configFullNameCache.get(configFullName);
     }
 
     /**
      * Sets a config object in the cache for the specified config file path.
-     * @param {string} configFilePath the absolute path to the config file
+     * @param {string} configFullName the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
+     * or the absolute path to a config file. This should uniquely identify a config.
      * @param {Object} config the config object to add to the cache
      * @returns {void}
      * @private
      */
-    setConfig(configFilePath, config) {
-        this.filePathCache.set(configFilePath, config);
+    setConfig(configFullName, config) {
+        this.configFullNameCache.set(configFullName, config);
     }
 
     /**
diff --git a/lib/config/config-file.js b/lib/config/config-file.js
index 832529952..3c790cf3b 100644
--- a/lib/config/config-file.js
+++ b/lib/config/config-file.js
@@ -488,12 +488,15 @@ function normalizePackageName(name, prefix) {
  * @returns {Object} An object containing 3 properties:
  * - 'filePath' (required) the resolved path that can be used directly to load the configuration.
  * - 'configName' the name of the configuration inside the plugin.
- * - 'configFullName' the name of the configuration as used in the eslint config (e.g. 'plugin:node/recommended').
+ * - 'configFullName' (required) the name of the configuration as used in the eslint config(e.g. 'plugin:node/recommended'),
+ *     or the absolute path to a config file. This should uniquely identify a config.
  * @private
  */
 function resolve(filePath, relativeTo) {
     if (isFilePath(filePath)) {
-        return { filePath: path.resolve(relativeTo || "", filePath) };
+        const fullPath = path.resolve(relativeTo || "", filePath);
+
+        return { filePath: fullPath, configFullName: fullPath };
     }
     let normalizedPackageName;
 
@@ -510,7 +513,7 @@ function resolve(filePath, relativeTo) {
     normalizedPackageName = normalizePackageName(filePath, "eslint-config");
     debug(`Attempting to resolve ${normalizedPackageName}`);
     filePath = resolver.resolve(normalizedPackageName, getLookupPath(relativeTo));
-    return { filePath };
+    return { filePath, configFullName: filePath };
 
 
 }
@@ -560,11 +563,12 @@ function loadFromDisk(resolvedPath, configContext) {
 /**
  * Loads a config object, applying extends if present.
  * @param {Object} configObject a config object to load
+ * @param {Config} configContext Context for the config instance
  * @returns {Object} the config object with extends applied if present, or the passed config if not
  * @private
  */
-function loadObject(configObject) {
-    return configObject.extends ? applyExtends(configObject, "") : configObject;
+function loadObject(configObject, configContext) {
+    return configObject.extends ? applyExtends(configObject, configContext, "") : configObject;
 }
 
 /**
@@ -579,7 +583,7 @@ function loadObject(configObject) {
 function load(filePath, configContext, relativeTo) {
     const resolvedPath = resolve(filePath, relativeTo);
 
-    const cachedConfig = configContext.configCache.getConfig(resolvedPath.filePath);
+    const cachedConfig = configContext.configCache.getConfig(resolvedPath.configFullName);
 
     if (cachedConfig) {
         return cachedConfig;
@@ -590,7 +594,7 @@ function load(filePath, configContext, relativeTo) {
     if (config) {
         config.filePath = resolvedPath.filePath;
         config.baseDirectory = path.dirname(resolvedPath.filePath);
-        configContext.configCache.setConfig(resolvedPath.filePath, config);
+        configContext.configCache.setConfig(resolvedPath.configFullName, config);
     }
 
     return config;
diff --git a/lib/linter.js b/lib/linter.js
index d2f1f4657..b8b3a5676 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -1205,6 +1205,7 @@ class Linter extends EventEmitter {
             fixedResult,
             fixed = false,
             passNumber = 0;
+        const debugTextDescription = options && options.filename || `${text.slice(0, 10)}...`;
 
         /**
          * This loop continues until one of the following is true:
@@ -1218,10 +1219,10 @@ class Linter extends EventEmitter {
         do {
             passNumber++;
 
-            debug(`Linting code for ${options.filename} (pass ${passNumber})`);
+            debug(`Linting code for ${debugTextDescription} (pass ${passNumber})`);
             messages = this.verify(text, config, options);
 
-            debug(`Generating fixed text for ${options.filename} (pass ${passNumber})`);
+            debug(`Generating fixed text for ${debugTextDescription} (pass ${passNumber})`);
             fixedResult = SourceCodeFixer.applyFixes(this.getSourceCode(), messages);
 
             // stop if there are any syntax errors.
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index 55225b8cc..142f0f24a 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -116,6 +116,15 @@ module.exports = {
                                 return null;
                             }
 
+                            if (node.object.type === "Identifier" && node.object.name === "let") {
+
+                                /*
+                                 * A statement that starts with `let[` is parsed as a destructuring variable declaration, not
+                                 * a MemberExpression.
+                                 */
+                                return null;
+                            }
+
                             return fixer.replaceTextRange(
                                 [dot.range[0], node.property.range[1]],
                                 `[${textAfterDot}"${node.property.name}"]`
diff --git a/lib/rules/getter-return.js b/lib/rules/getter-return.js
new file mode 100644
index 000000000..8a095ad29
--- /dev/null
+++ b/lib/rules/getter-return.js
@@ -0,0 +1,151 @@
+/**
+ * @fileoverview Enforces that a return statement is present in property getters.
+ * @author Aladdin-ADD(hh_2013@foxmail.com)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+/**
+ * Checks a given code path segment is reachable.
+ *
+ * @param {CodePathSegment} segment - A segment to check.
+ * @returns {boolean} `true` if the segment is reachable.
+ */
+function isReachable(segment) {
+    return segment.reachable;
+}
+
+/**
+ * Gets a readable location.
+ *
+ * - FunctionExpression -> the function name or `function` keyword.
+ *
+ * @param {ASTNode} node - A function node to get.
+ * @returns {ASTNode|Token} The node or the token of a location.
+ */
+function getId(node) {
+    return node.id || node;
+}
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "enforce `return` statements in getters",
+            category: "Possible Errors",
+            recommended: false
+        },
+        fixable: null,
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowImplicit: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
+    },
+
+    create(context) {
+
+        const options = context.options[0] || { allowImplicit: false };
+
+        let funcInfo = {
+            upper: null,
+            codePath: null,
+            hasReturn: false,
+            shouldCheck: false,
+            node: null
+        };
+
+        /**
+         * Checks whether or not the last code path segment is reachable.
+         * Then reports this function if the segment is reachable.
+         *
+         * If the last code path segment is reachable, there are paths which are not
+         * returned or thrown.
+         *
+         * @param {ASTNode} node - A node to check.
+         * @returns {void}
+         */
+        function checkLastSegment(node) {
+            if (funcInfo.shouldCheck &&
+                funcInfo.codePath.currentSegments.some(isReachable)
+            ) {
+                context.report({
+                    node,
+                    loc: getId(node).loc.start,
+                    message: funcInfo.hasReturn
+                        ? "Expected {{name}} to always return a value."
+                        : "Expected to return a value in {{name}}.",
+                    data: {
+                        name: astUtils.getFunctionNameWithKind(funcInfo.node)
+                    }
+                });
+            }
+        }
+
+        return {
+
+            // Stacks this function's information.
+            onCodePathStart(codePath, node) {
+                const parent = node.parent;
+
+                funcInfo = {
+                    upper: funcInfo,
+                    codePath,
+                    hasReturn: false,
+                    shouldCheck:
+                        node.type === "FunctionExpression" &&
+                        node.body.type === "BlockStatement" &&
+
+                        // check if it is a "getter", or a method named "get".
+                        (parent.kind === "get" || astUtils.getStaticPropertyName(parent) === "get"),
+                    node
+                };
+            },
+
+            // Pops this function's information.
+            onCodePathEnd() {
+                funcInfo = funcInfo.upper;
+            },
+
+            // Checks the return statement is valid.
+            ReturnStatement(node) {
+                if (funcInfo.shouldCheck) {
+                    funcInfo.hasReturn = true;
+
+                    // if allowImplicit: false, should also check node.argument
+                    if (!options.allowImplicit && !node.argument) {
+                        context.report({
+                            node,
+                            message: "Expected to return a value in {{name}}.",
+                            data: {
+                                name: astUtils.getFunctionNameWithKind(funcInfo.node)
+                            }
+                        });
+                    }
+                }
+            },
+
+            // Reports a given function if the last path is reachable.
+            "FunctionExpression:exit": checkLastSegment
+        };
+    }
+};
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 3027e7720..6babe29c9 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -303,6 +303,18 @@ class OffsetStorage {
         }
     }
 
+    /**
+     * Sets the desired offset of a token, ignoring the usual collapsing behavior.
+     * **WARNING**: This is usually not what you want to use. See `setDesiredOffset` instead.
+     * @param {Token} token The token
+     * @param {Token} offsetFrom The token that `token` should be offset from
+     * @param {number} offset The desired indent level
+     * @returns {void}
+     */
+    forceSetDesiredOffset(token, offsetFrom, offset) {
+        this.desiredOffsets[token.range[0]] = { offset, from: offsetFrom };
+    }
+
     /**
     * Sets the desired offset of multiple tokens
     * @param {Token[]} tokens A list of tokens. These tokens should be consecutive.
@@ -884,7 +896,7 @@ module.exports = {
 
             parameterParens.add(openingParen);
             parameterParens.add(closingParen);
-            offsets.matchIndentOf(sourceCode.getLastToken(node.callee), openingParen);
+            offsets.matchIndentOf(sourceCode.getTokenBefore(openingParen), openingParen);
 
             addElementListIndent(node.arguments, openingParen, closingParen, options.CallExpression.arguments);
         }
@@ -1274,7 +1286,35 @@ module.exports = {
             VariableDeclaration(node) {
                 const variableIndent = options.VariableDeclarator.hasOwnProperty(node.kind) ? options.VariableDeclarator[node.kind] : DEFAULT_VARIABLE_INDENT;
 
-                offsets.setDesiredOffsets(getTokensAndComments(node), sourceCode.getFirstToken(node), variableIndent);
+                if (node.declarations[node.declarations.length - 1].loc.start.line > node.loc.start.line) {
+
+                    /*
+                     * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
+                     * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
+                     * the following indentations are correct:
+                     *
+                     * var foo = {
+                     *   ok: true
+                     * };
+                     *
+                     * var foo = {
+                     *     ok: true,
+                     *   },
+                     *   bar = 1;
+                     *
+                     * Account for when exiting the AST (after indentations have already been set for the nodes in
+                     * the declaration) by manually increasing the indentation level of the tokens in this declarator
+                     * on the same line as the start of the declaration, provided that there are declarators that
+                     * follow this one.
+                     */
+                    getTokensAndComments(node).forEach((token, index, tokens) => {
+                        if (index !== 0) {
+                            offsets.forceSetDesiredOffset(token, tokens[0], variableIndent);
+                        }
+                    });
+                } else {
+                    offsets.setDesiredOffsets(getTokensAndComments(node), sourceCode.getFirstToken(node), variableIndent);
+                }
                 const lastToken = sourceCode.getLastToken(node);
 
                 if (astUtils.isSemicolonToken(lastToken)) {
@@ -1294,37 +1334,6 @@ module.exports = {
                 }
             },
 
-            "VariableDeclarator:exit"(node) {
-
-                /*
-                 * VariableDeclarator indentation is a bit different from other forms of indentation, in that the
-                 * indentation of an opening bracket sometimes won't match that of a closing bracket. For example,
-                 * the following indentations are correct:
-                 *
-                 * var foo = {
-                 *   ok: true
-                 * };
-                 *
-                 * var foo = {
-                 *     ok: true,
-                 *   },
-                 *   bar = 1;
-                 *
-                 * Account for when exiting the AST (after indentations have already been set for the nodes in
-                 * the declaration) by manually increasing the indentation level of the tokens in the first declarator if the
-                 * parent declaration has more than one declarator.
-                 */
-                if (node.parent.declarations.length > 1 && node.parent.declarations[0] === node && node.init) {
-                    const valueTokens = new Set(getTokensAndComments(node.init));
-
-                    valueTokens.forEach(token => {
-                        if (!valueTokens.has(offsets.getFirstDependency(token))) {
-                            offsets.increaseOffset(token, options.VariableDeclarator[node.parent.kind]);
-                        }
-                    });
-                }
-            },
-
             WhileStatement: node => addBlocklessNodeIndent(node.body),
 
             "*:exit": checkForUnknownNode,
diff --git a/lib/rules/no-debugger.js b/lib/rules/no-debugger.js
index a158724fe..d79cb1816 100644
--- a/lib/rules/no-debugger.js
+++ b/lib/rules/no-debugger.js
@@ -5,6 +5,8 @@
 
 "use strict";
 
+const astUtils = require("../ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -28,7 +30,10 @@ module.exports = {
                     node,
                     message: "Unexpected 'debugger' statement.",
                     fix(fixer) {
-                        return fixer.remove(node);
+                        if (astUtils.STATEMENT_LIST_PARENTS.has(node.parent.type)) {
+                            return fixer.remove(node);
+                        }
+                        return null;
                     }
                 });
             }
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 9f48f1d81..fd6fd0b78 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -426,7 +426,7 @@ module.exports = {
                     secondToken.type === "Keyword" && (
                         secondToken.value === "function" ||
                         secondToken.value === "class" ||
-                        secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken))
+                        secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken))
                     )
                 )
             ) {
@@ -512,16 +512,27 @@ module.exports = {
             ExportDefaultDeclaration: node => checkExpressionOrExportStatement(node.declaration),
             ExpressionStatement: node => checkExpressionOrExportStatement(node.expression),
 
-            ForInStatement(node) {
-                if (hasExcessParens(node.right)) {
-                    report(node.right);
-                }
-                if (hasExcessParens(node.left)) {
-                    report(node.left);
-                }
-            },
+            "ForInStatement, ForOfStatement"(node) {
+                if (node.left.type !== "VariableDeclarator") {
+                    const firstLeftToken = sourceCode.getFirstToken(node.left, astUtils.isNotOpeningParenToken);
+
+                    if (
+                        firstLeftToken.value === "let" && (
+
+                            // If `let` is the only thing on the left side of the loop, it's the loop variable: `for ((let) of foo);`
+                            // Removing it will cause a syntax error, because it will be parsed as the start of a VariableDeclarator.
+                            firstLeftToken.range[1] === node.left.range[1] ||
 
-            ForOfStatement(node) {
+                            // If `let` is followed by a `[` token, it's a property access on the `let` value: `for ((let[foo]) of bar);`
+                            // Removing it will cause the property access to be parsed as a destructuring declaration of `foo` instead.
+                            astUtils.isOpeningBracketToken(
+                                sourceCode.getTokenAfter(firstLeftToken, astUtils.isNotClosingParenToken)
+                            )
+                        )
+                    ) {
+                        tokensToIgnore.add(firstLeftToken);
+                    }
+                }
                 if (hasExcessParens(node.right)) {
                     report(node.right);
                 }
diff --git a/package.json b/package.json
index b538001f6..15c0eb561 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.1.0",
+  "version": "4.1.1",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {


Test patch:
diff --git a/tests/fixtures/config-file/plugins/.eslintrc2.yml b/tests/fixtures/config-file/plugins/.eslintrc2.yml
new file mode 100644
index 000000000..973d77096
--- /dev/null
+++ b/tests/fixtures/config-file/plugins/.eslintrc2.yml
@@ -0,0 +1,3 @@
+extends:
+    - plugin:test/foo
+    - plugin:test/bar
diff --git a/tests/lib/config.js b/tests/lib/config.js
index 935c210bd..59e1a47d0 100644
--- a/tests/lib/config.js
+++ b/tests/lib/config.js
@@ -158,6 +158,24 @@ describe("Config", () => {
             assert.deepEqual(customBaseConfig, { foo: "bar" });
             assert.equal(configHelper.options.format, "foo");
         });
+
+        it("should create config object when using baseConfig with extends", () => {
+            const customBaseConfig = {
+                extends: path.resolve(__dirname, "..", "fixtures", "config-extends", "array", ".eslintrc")
+            };
+            const configHelper = new Config({ baseConfig: customBaseConfig }, linter);
+
+            assert.deepEqual(configHelper.baseConfig.env, {
+                browser: false,
+                es6: true,
+                node: true
+            });
+            assert.deepEqual(configHelper.baseConfig.rules, {
+                "no-empty": 1,
+                "comma-dangle": 2,
+                "no-console": 2
+            });
+        });
     });
 
     describe("findLocalConfigFiles()", () => {
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index a9d2baa0d..23a206b05 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -899,6 +899,49 @@ describe("ConfigFile", () => {
                     }
                 });
             });
+
+            it("should load two separate configs from a plugin", () => {
+                const stubConfig = new Config({}, new Linter());
+                const resolvedPath = path.resolve(PROJECT_PATH, "./node_modules/eslint-plugin-test/index.js");
+
+                const configDeps = {
+                    "../util/module-resolver": createStubModuleResolver({
+                        "eslint-plugin-test": resolvedPath
+                    }),
+                    "require-uncached"(filename) {
+                        return configDeps[filename];
+                    }
+                };
+
+                configDeps[resolvedPath] = {
+                    configs: {
+                        foo: { rules: { semi: 2, quotes: 1 } },
+                        bar: { rules: { quotes: 2, yoda: 2 } }
+                    }
+                };
+
+                const StubbedConfigFile = proxyquire("../../../lib/config/config-file", configDeps);
+
+                const configFilePath = getFixturePath("plugins/.eslintrc2.yml");
+                const config = StubbedConfigFile.load(configFilePath, stubConfig);
+
+                assert.deepEqual(config, {
+                    baseDirectory: path.dirname(configFilePath),
+                    filePath: configFilePath,
+                    parserOptions: {},
+                    globals: {},
+                    env: {},
+                    rules: {
+                        semi: 2,
+                        quotes: 2,
+                        yoda: 2
+                    },
+                    extends: [
+                        "plugin:test/foo",
+                        "plugin:test/bar"
+                    ]
+                });
+            });
         });
 
         describe("even if config files have Unicode BOM,", () => {
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 4acc46b3f..88ce63cb1 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -3776,6 +3776,20 @@ describe("eslint", () => {
             assert.equal(messages.output, "var a;", "Fixes were applied correctly");
             assert.isTrue(messages.fixed);
         });
+
+        it("does not require a third argument", () => {
+            const fixResult = linter.verifyAndFix("var a", {
+                rules: {
+                    semi: 2
+                }
+            });
+
+            assert.deepEqual(fixResult, {
+                fixed: true,
+                messages: [],
+                output: "var a;"
+            });
+        });
     });
 
     describe("Edge cases", () => {
diff --git a/tests/lib/rules/dot-notation.js b/tests/lib/rules/dot-notation.js
index f20334f66..08013540a 100644
--- a/tests/lib/rules/dot-notation.js
+++ b/tests/lib/rules/dot-notation.js
@@ -178,6 +178,12 @@ ruleTester.run("dot-notation", rule, {
             code: "foo['bar']instanceof baz",
             output: "foo.bar instanceof baz",
             errors: [{ message: "[\"bar\"] is better written in dot notation." }]
+        },
+        {
+            code: "let.if()",
+            output: null, // `let["if"]()` is a syntax error because `let[` indicates a destructuring variable declaration
+            options: [{ allowKeywords: false }],
+            errors: [{ message: ".if is a syntax error." }]
         }
     ]
 });
diff --git a/tests/lib/rules/getter-return.js b/tests/lib/rules/getter-return.js
new file mode 100644
index 000000000..646d44893
--- /dev/null
+++ b/tests/lib/rules/getter-return.js
@@ -0,0 +1,109 @@
+/**
+ * @fileoverview Enforces that a return statement is present in property getters.
+ * @author Aladdin-ADD(hh_2013@foxmail.com)
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/getter-return");
+const RuleTester = require("../../../lib/testers/rule-tester");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// data is not working, so specify a name: "getter 'bar'"
+const name = "getter 'bar'";
+const noReturnMessage = `Expected to return a value in ${name}.`;
+const noLastReturnMessage = `Expected ${name} to always return a value.`;
+const options = [{ allowImplicit: true }];
+
+ruleTester.run("getter-return", rule, {
+
+    valid: [
+
+        // test obj: get
+        // option: {allowImplicit: false}
+        { code: "var foo = { get bar(){return true;} };" },
+
+        // option: {allowImplicit: true}
+        { code: "var foo = { get bar() {return;} };", options },
+        { code: "var foo = { get bar(){return true;} };", options },
+        { code: "var foo = { get bar(){if(bar) {return;} return true;} };", options },
+
+        // test class: get
+        // option: {allowImplicit: false}
+        { code: "class foo { get bar(){return true;} }" },
+        { code: "class foo { get bar(){if(baz){return true;} else {return false;} } }" },
+        { code: "class foo { get(){return true;} }" },
+
+        // option: {allowImplicit: true}
+        { code: "class foo { get bar(){return true;} }", options },
+        { code: "class foo { get bar(){return;} }", options },
+
+        // test object.defineProperty(s)
+        // option: {allowImplicit: false}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});" },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function () { ~function (){ return true; }();return true;}});" },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {return true;}} });" },
+        { code: "Object.defineProperies(foo, { bar: { get: function () { ~function (){ return true; }(); return true;}} });" },
+
+        // option: {allowImplicit: true}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function () {return true;}});", options },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){return;}});", options },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {return true;}} });", options },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {return;}} });", options },
+
+        // not getter.
+        { code: "var get = function(){};" },
+        { code: "var get = function(){ return true; };" },
+        { code: "var foo = { bar(){} };" },
+        { code: "var foo = { bar(){ return true; } };" },
+        { code: "var foo = { bar: function(){} };" },
+        { code: "var foo = { bar(){ return true; } };" },
+        { code: "var foo = { bar: function(){return;} };" },
+        { code: "var foo = { bar: function(){return true;} };" }
+    ],
+
+    invalid: [
+
+        // test obj: get
+        // option: {allowImplicit: false}
+        { code: "var foo = { get bar() {} };", errors: [{ message: noReturnMessage }] },
+        { code: "var foo = { get bar(){if(baz) {return true;}} };", errors: [{ message: noLastReturnMessage }] },
+        { code: "var foo = { get bar() { ~function () {return true;}} };", errors: [{ message: noReturnMessage }] },
+
+        // option: {allowImplicit: true}
+        { code: "var foo = { get bar() {} };", options, errors: [{ message: noReturnMessage }] },
+        { code: "var foo = { get bar() {if (baz) {return;}} };", options, errors: [{ message: noLastReturnMessage }] },
+
+        // test class: get
+        // option: {allowImplicit: false}
+        { code: "class foo { get bar(){} }", errors: [{ message: noReturnMessage }] },
+        { code: "class foo { get bar(){ if (baz) { return true; }}}", errors: [{ noLastReturnMessage }] },
+        { code: "class foo { get bar(){ ~function () { return true; }()}}", errors: [{ noLastReturnMessage }] },
+
+        // option: {allowImplicit: true}
+        { code: "class foo { get bar(){} }", options, errors: [{ message: noReturnMessage }] },
+        { code: "class foo { get bar(){if (baz) {return true;} } }", options, errors: [{ message: noLastReturnMessage }] },
+
+        // test object.defineProperty(s)
+        // option: {allowImplicit: false}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", errors: [{ noReturnMessage }] },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){if(bar) {return true;}}});", errors: [{ message: "Expected method 'get' to always return a value." }] },
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){ ~function () { return true; }()}});", errors: [{ noReturnMessage }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {}} });", options, errors: [{ noReturnMessage }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function (){if(bar) {return true;}}}});", options, errors: [{ message: "Expected method 'get' to always return a value." }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {~function () { return true; }()}} });", options, errors: [{ noReturnMessage }] },
+
+        // option: {allowImplicit: true}
+        { code: "Object.defineProperty(foo, \"bar\", { get: function (){}});", options, errors: [{ message: "Expected to return a value in method 'get'." }] },
+        { code: "Object.defineProperies(foo, { bar: { get: function () {}} });", options, errors: [{ noReturnMessage }] }
+    ]
+});
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 12b42d712..e655f3f00 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -518,6 +518,64 @@ ruleTester.run("indent", rule, {
             `,
             options: [2, { VariableDeclarator: 2, SwitchCase: 1 }]
         },
+        {
+            code: unIndent`
+                var
+                    x = {
+                        a: 1,
+                    },
+                    y = {
+                        b: 2
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                const
+                    x = {
+                        a: 1,
+                    },
+                    y = {
+                        b: 2
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                let
+                    x = {
+                        a: 1,
+                    },
+                    y = {
+                        b: 2
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                var foo = { a: 1 }, bar = {
+                    b: 2
+                };
+            `
+        },
+        {
+            code: unIndent`
+                var foo = { a: 1 }, bar = {
+                        b: 2
+                    },
+                    baz = {
+                        c: 3
+                    }
+            `
+        },
+        {
+            code: unIndent`
+                const {
+                        foo
+                    } = 1,
+                    bar = 2
+            `
+        },
         {
             code: unIndent`
                 var foo = 1,
@@ -2102,6 +2160,33 @@ ruleTester.run("indent", rule, {
             `,
             options: [2]
         },
+        {
+            code: unIndent`
+                (
+                    foo
+                )(
+                    bar
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (() =>
+                    foo
+                )(
+                    bar
+                )
+            `
+        },
+        {
+            code: unIndent`
+                (() => {
+                    foo();
+                })(
+                    bar
+                )
+            `
+        },
         {
 
             // Don't lint the indentation of the first token after a :
@@ -7951,6 +8036,57 @@ ruleTester.run("indent", rule, {
             `,
             errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
         },
+        {
+            code: unIndent`
+                (
+                    foo
+                )(
+                        bar
+                    )
+            `,
+            output: unIndent`
+                (
+                    foo
+                )(
+                    bar
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                (() =>
+                    foo
+                )(
+                        bar
+                    )
+            `,
+            output: unIndent`
+                (() =>
+                    foo
+                )(
+                    bar
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                (() => {
+                    foo();
+                })(
+                        bar
+                    )
+            `,
+            output: unIndent`
+                (() => {
+                    foo();
+                })(
+                    bar
+                )
+            `,
+            errors: expectedErrors([[4, 4, 8, "Identifier"], [5, 0, 4, "Punctuator"]])
+        },
         {
             code: unIndent`
                 foo.
diff --git a/tests/lib/rules/no-debugger.js b/tests/lib/rules/no-debugger.js
index a907405c8..9edf059c4 100644
--- a/tests/lib/rules/no-debugger.js
+++ b/tests/lib/rules/no-debugger.js
@@ -23,6 +23,15 @@ ruleTester.run("no-debugger", rule, {
         "var test = { debugger: 1 }; test.debugger;"
     ],
     invalid: [
-        { code: "debugger", errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }], output: "" }
+        {
+            code: "debugger",
+            output: "",
+            errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }]
+        },
+        {
+            code: "if (foo) debugger",
+            output: null,
+            errors: [{ message: "Unexpected 'debugger' statement.", type: "DebuggerStatement" }]
+        }
     ]
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 54990b96e..be32041c8 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -450,7 +450,12 @@ ruleTester.run("no-extra-parens", rule, {
         {
             code: "((function(){}).foo)();",
             options: ["functions"]
-        }
+        },
+        "(let)[foo]",
+        "for ((let) in foo);",
+        "for ((let[foo]) in bar);",
+        "for ((let)[foo] in bar);",
+        "for ((let[foo].bar) in baz);"
     ],
 
     invalid: [
@@ -1052,6 +1057,24 @@ ruleTester.run("no-extra-parens", rule, {
             "MemberExpression",
             1,
             { parserOptions: { ecmaVersion: 2015 } }
+        ),
+        invalid(
+            "(let).foo",
+            "let.foo",
+            "Identifier",
+            1
+        ),
+        invalid(
+            "for ((let.foo) in bar);",
+            "for (let.foo in bar);",
+            "MemberExpression",
+            1
+        ),
+        invalid(
+            "for ((let).foo.bar in baz);",
+            "for (let.foo.bar in baz);",
+            "Identifier",
+            1
         )
     ]
 });