Repository: eslint/eslint
Pull request #10652 (2018-07-22 22:34:17)

Docs: Remove superfluous object option sample code
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

- [x] Documentation update
- [ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
- [ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
- [ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
- [ ] Add autofixing to a rule
- [ ] Add a CLI option
- [ ] Add something to the core
- [ ] Other, please explain:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

The other rules don't have this sample code block, so this PR makes the `no-else-return` documentation consistent with the others.

**Is there anything you'd like reviewers to focus on?**





Patch:
diff --git a/Makefile.js b/Makefile.js
index 9c48620ef..cd756a746 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -684,7 +684,7 @@ target.gensite = function(prereleaseVersion) {
     const rules = require(".").linter.getRules();
 
     const RECOMMENDED_TEXT = "\n\n(recommended) The `\"extends\": \"eslint:recommended\"` property in a configuration file enables this rule.";
-    const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fix) can automatically fix some of the problems reported by this rule.";
+    const FIXABLE_TEXT = "\n\n(fixable) The `--fix` option on the [command line](../user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.";
 
     // 4. Loop through all files in temporary directory
     process.stdout.write("> Updating files (Steps 4-9): 0/... - ...\r");
diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js
index 28d7aa4f9..8b74d8ba9 100644
--- a/conf/eslint-recommended.js
+++ b/conf/eslint-recommended.js
@@ -83,6 +83,7 @@ module.exports = {
         "newline-per-chained-call": "off",
         "no-alert": "off",
         "no-array-constructor": "off",
+        "no-async-promise-executor": "off",
         "no-await-in-loop": "off",
         "no-bitwise": "off",
         "no-buffer-constructor": "off",
@@ -240,6 +241,7 @@ module.exports = {
         "quote-props": "off",
         quotes: "off",
         radix: "off",
+        "require-atomic-updates": "off",
         "require-await": "off",
         "require-jsdoc": "off",
         "require-yield": "error",
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index e56fd5789..2e3390b93 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -330,7 +330,7 @@ var CLIEngine = require("eslint").CLIEngine;
 The `CLIEngine` is a constructor, and you can create a new instance by passing in the options you want to use. The available options are:
 
 * `allowInlineConfig` - Set to `false` to disable the use of configuration comments (such as `/*eslint-disable*/`). Corresponds to `--no-inline-config`.
-* `baseConfig` - Set to false to disable use of base config. Could be set to an object to override default base config as well.
+* `baseConfig` - Can optionally be set to a config object. This will used as a default config, and will be merged with any configuration defined in `.eslintrc.*` files, with the `.eslintrc.*` files having precedence.
 * `cache` - Operate only on changed files (default: `false`). Corresponds to `--cache`.
 * `cacheFile` - Name of the file where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-file`. Deprecated: use `cacheLocation` instead.
 * `cacheLocation` - Name of the file or directory where the cache will be stored (default: `.eslintcache`). Corresponds to `--cache-location`.
diff --git a/docs/rules/dot-location.md b/docs/rules/dot-location.md
index c4283cf7f..1fe4a3129 100644
--- a/docs/rules/dot-location.md
+++ b/docs/rules/dot-location.md
@@ -20,7 +20,7 @@ This rule aims to enforce newline consistency in member expressions. This rule p
 
 The rule takes one option, a string:
 
-* If it is `"object"`, the dot in a member expression should be on the same line as the object portion. The default is `"object"`.
+* If it is `"object"` (default), the dot in a member expression should be on the same line as the object portion.
 * If it is `"property"`, the dot in a member expression should be on the same line as the property portion.
 
 ### object
diff --git a/docs/rules/no-async-promise-executor.md b/docs/rules/no-async-promise-executor.md
new file mode 100644
index 000000000..82fed5369
--- /dev/null
+++ b/docs/rules/no-async-promise-executor.md
@@ -0,0 +1,62 @@
+# disallow using an async function as a Promise executor (no-async-promise-executor)
+
+The `new Promise` constructor accepts an *executor* function as an argument, which has `resolve` and `reject` parameters that can be used to control the state of the created Promise. For example:
+
+```js
+const result = new Promise(function executor(resolve, reject) {
+  readFile('foo.txt', function(err, result) {
+    if (err) {
+      reject(err);
+    } else {
+      resolve(result);
+    }
+  });
+});
+```
+
+The executor function can also be an `async function`. However, this is usually a mistake, for a few reasons:
+
+* If an async executor function throws an error, the error will be lost and won't cause the newly-constructed `Promise` to reject. This could make it difficult to debug and handle some errors.
+* If a Promise executor function is using `await`, this is usually a sign that it is not actually necessary to use the `new Promise` constructor, or the scope of the `new Promise` constructor can be reduced.
+
+## Rule Details
+
+This rule aims to disallow async Promise executor functions.
+
+Examples of **incorrect** code for this rule:
+
+```js
+const result = new Promise(async (resolve, reject) => {
+  readFile('foo.txt', function(err, result) {
+    if (err) {
+      reject(err);
+    } else {
+      resolve(result);
+    }
+  });
+});
+
+const result = new Promise(async (resolve, reject) => {
+  resolve(await foo);
+});
+```
+
+Examples of **correct** code for this rule:
+
+```js
+const result = new Promise((resolve, reject) => {
+  readFile('foo.txt', function(err, result) {
+    if (err) {
+      reject(err);
+    } else {
+      resolve(result);
+    }
+  });
+});
+
+const result = Promise.resolve(foo);
+```
+
+## When Not To Use It
+
+If your codebase doesn't support async function syntax, there's no need to enable this rule.
diff --git a/docs/rules/no-else-return.md b/docs/rules/no-else-return.md
index eb6f8fab3..2f3f83d73 100644
--- a/docs/rules/no-else-return.md
+++ b/docs/rules/no-else-return.md
@@ -20,14 +20,6 @@ This rule is aimed at highlighting an unnecessary block of code following an `if
 
 This rule has an object option:
 
-```json
-{
-    "no-else-return": ["error", { "allowElseIf": true }],
-    // or
-    "no-else-return": ["error", { "allowElseIf": false }]
-}
-```
-
 * `allowElseIf: true` (default) allows `else if` blocks after a return
 * `allowElseIf: false` disallows `else if` blocks after a return
 
diff --git a/docs/rules/no-prototype-builtins.md b/docs/rules/no-prototype-builtins.md
index 1694356a3..e2dd4f5b9 100644
--- a/docs/rules/no-prototype-builtins.md
+++ b/docs/rules/no-prototype-builtins.md
@@ -1,6 +1,6 @@
 # Disallow use of Object.prototypes builtins directly (no-prototype-builtins)
 
-In ECMAScript 5.1, `Object.create` was added, which enables the creation of objects with a specified `[[Prototype]]`. `Object.create(null)` is a common pattern used to create objects that will be used as a Map. This can lead to errors when it is assumed that objects will have properties from `Object.prototype`. This rule prevents calling `Object.prototype` methods directly from an object.
+In ECMAScript 5.1, `Object.create` was added, which enables the creation of objects with a specified `[[Prototype]]`. `Object.create(null)` is a common pattern used to create objects that will be used as a Map. This can lead to errors when it is assumed that objects will have properties from `Object.prototype`. This rule prevents calling some `Object.prototype` methods directly from an object.
 
 ## Rule Details
 
diff --git a/docs/rules/require-atomic-updates.md b/docs/rules/require-atomic-updates.md
new file mode 100644
index 000000000..593ccdd60
--- /dev/null
+++ b/docs/rules/require-atomic-updates.md
@@ -0,0 +1,97 @@
+# Disallow assignments that can lead to race conditions due to usage of `await` or `yield` (require-atomic-updates)
+
+When writing asynchronous code, it is possible to create subtle race condition bugs. Consider the following example:
+
+```js
+let totalLength = 0;
+
+async function addLengthOfSinglePage(pageNum) {
+  totalLength += await getPageLength(pageNum);
+}
+
+Promise.all([addLengthOfSinglePage(1), addLengthOfSinglePage(2)]).then(() => {
+  console.log('The combined length of both pages is', totalLength);
+});
+```
+
+This code looks like it will sum the results of calling `getPageLength(1)` and `getPageLength(2)`, but in reality the final value of `totalLength` will only be the length of one of the two pages. The bug is in the statement `totalLength += await getPageLength(pageNum);`. This statement first reads an initial value of `totalLength`, then calls `getPageLength(pageNum)` and waits for that Promise to fulfill. Finally, it sets the value of `totalLength` to the sum of `await getPageLength(pageNum)` and the *initial* value of `totalLength`. If the `totalLength` variable is updated in a separate function call during the time that the `getPageLength(pageNum)` Promise is pending, that update will be lost because the new value is overwritten without being read.
+
+One way to fix this issue would be to ensure that `totalLength` is read at the same time as it's updated, like this:
+
+```js
+async function addLengthOfSinglePage(pageNum) {
+  const lengthOfThisPage = await getPageLength(pageNum);
+
+  totalLength += lengthOfThisPage;
+}
+```
+
+Another solution would be to avoid using a mutable variable reference at all:
+
+```js
+Promise.all([getPageLength(1), getPageLength(2)]).then(pageLengths => {
+  const totalLength = pageLengths.reduce((accumulator, length) => accumulator + length, 0);
+
+  console.log('The combined length of both pages is', totalLength);
+});
+```
+
+## Rule Details
+
+This rule aims to report assignments to variables or properties where all of the following are true:
+
+* A variable or property is reassigned to a new value which is based on its old value.
+* A `yield` or `await` expression interrupts the assignment after the old value is read, and before the new value is set.
+* The rule cannot easily verify that the assignment is safe (e.g. if an assigned variable is local and would not be readable from anywhere else while the function is paused).
+
+Examples of **incorrect** code for this rule:
+
+```js
+/* eslint require-atomic-updates: error */
+
+let result;
+async function foo() {
+  result += await somethingElse;
+
+  result = result + await somethingElse;
+
+  result = result + doSomething(await somethingElse);
+}
+
+function* bar() {
+  result += yield;
+
+  result = result + (yield somethingElse);
+
+  result = result + doSomething(yield somethingElse);
+}
+```
+
+Examples of **correct** code for this rule:
+
+```js
+/* eslint require-atomic-updates: error */
+
+let result;
+async function foo() {
+  result = await somethingElse + result;
+
+  let tmp = await somethingElse;
+  result += tmp;
+
+  let localVariable = 0;
+  localVariable += await somethingElse;
+}
+
+function* bar() {
+  result += yield;
+
+  result = (yield somethingElse) + result;
+
+  result = doSomething(yield somethingElse, result);
+}
+```
+
+## When Not To Use It
+
+If you don't use async or generator functions, you don't need to enable this rule.
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index b1284f92a..4daa7dd59 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -272,6 +272,8 @@ ESLint comes with a large number of rules. You can modify which rules your proje
 * `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
 * `"error"` or `2` - turn the rule on as an error (exit code is 1 when triggered)
 
+### Using Configuration Comments
+
 To configure rules inside of a file using configuration comments, use a comment in the following format:
 
 ```js
@@ -294,6 +296,8 @@ If a rule has additional options, you can specify them using array literal synta
 
 This comment specifies the "double" option for the [`quotes`](../rules/quotes) rule. The first item in the array is always the rule severity (number or string).
 
+### Using Configuration Files
+
 To configure rules inside of a configuration file, use the `rules` key along with an error level and any options you want to use. For example:
 
 
diff --git a/lib/cli-engine.js b/lib/cli-engine.js
index 8442de2de..aa0f5521c 100644
--- a/lib/cli-engine.js
+++ b/lib/cli-engine.js
@@ -41,7 +41,7 @@ const resolver = new ModuleResolver();
  * The options to configure a CLI engine with.
  * @typedef {Object} CLIEngineOptions
  * @property {boolean} allowInlineConfig Enable or disable inline configuration comments.
- * @property {boolean|Object} baseConfig Base config object. True enables recommend rules and environments.
+ * @property {Object} baseConfig Base config object, extended by all configs used with this CLIEngine instance
  * @property {boolean} cache Enable result caching.
  * @property {string} cacheLocation The cache file to use instead of .eslintcache.
  * @property {string} configFile The configuration file to use.
diff --git a/lib/ignored-paths.js b/lib/ignored-paths.js
index 8134d6d32..4ef15ae75 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -79,6 +79,27 @@ function mergeDefaultOptions(options) {
     return Object.assign({}, DEFAULT_OPTIONS, options);
 }
 
+/**
+ * Converts a glob pattern to a new glob pattern relative to a different directory
+ * @param {string} globPattern The glob pattern, relative the the old base directory
+ * @param {string} relativePathToOldBaseDir A relative path from the new base directory to the old one
+ * @returns {string} A glob pattern relative to the new base directory
+ */
+function relativize(globPattern, relativePathToOldBaseDir) {
+    if (relativePathToOldBaseDir === "") {
+        return globPattern;
+    }
+
+    const prefix = globPattern.startsWith("!") ? "!" : "";
+    const globWithoutPrefix = globPattern.replace(/^!/, "");
+
+    if (globWithoutPrefix.startsWith("/")) {
+        return `${prefix}/${relativePathToOldBaseDir}${globWithoutPrefix}`;
+    }
+
+    return globPattern;
+}
+
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -96,41 +117,35 @@ class IgnoredPaths {
 
         this.cache = {};
 
-        /**
-         * add pattern to node-ignore instance
-         * @param {Object} ig, instance of node-ignore
-         * @param {string} pattern, pattern do add to ig
-         * @returns {array} raw ignore rules
-         */
-        function addPattern(ig, pattern) {
-            return ig.addPattern(pattern);
-        }
-
         this.defaultPatterns = [].concat(DEFAULT_IGNORE_DIRS, options.patterns || []);
-        this.baseDir = options.cwd;
+
+        this.ignoreFileDir = options.ignore !== false && options.ignorePath
+            ? path.dirname(path.resolve(options.cwd, options.ignorePath))
+            : options.cwd;
+        this.options = options;
 
         this.ig = {
             custom: ignore(),
             default: ignore()
         };
 
-        /*
-         * Add a way to keep track of ignored files.  This was present in node-ignore
-         * 2.x, but dropped for now as of 3.0.10.
-         */
-        this.ig.custom.ignoreFiles = [];
-        this.ig.default.ignoreFiles = [];
-
+        this.defaultPatterns.forEach(pattern => this.addPatternRelativeToCwd(this.ig.default, pattern));
         if (options.dotfiles !== true) {
 
             /*
              * ignore files beginning with a dot, but not files in a parent or
              * ancestor directory (which in relative format will begin with `../`).
              */
-            addPattern(this.ig.default, [".*", "!../"]);
+            this.addPatternRelativeToCwd(this.ig.default, ".*");
+            this.addPatternRelativeToCwd(this.ig.default, "!../");
         }
 
-        addPattern(this.ig.default, this.defaultPatterns);
+        /*
+         * Add a way to keep track of ignored files.  This was present in node-ignore
+         * 2.x, but dropped for now as of 3.0.10.
+         */
+        this.ig.custom.ignoreFiles = [];
+        this.ig.default.ignoreFiles = [];
 
         if (options.ignore !== false) {
             let ignorePath;
@@ -154,13 +169,11 @@ class IgnoredPaths {
                     debug(`Loaded ignore file ${ignorePath}`);
                 } catch (e) {
                     debug("Could not find ignore file in cwd");
-                    this.options = options;
                 }
             }
 
             if (ignorePath) {
                 debug(`Adding ${ignorePath}`);
-                this.baseDir = path.dirname(path.resolve(options.cwd, ignorePath));
                 this.addIgnoreFile(this.ig.custom, ignorePath);
                 this.addIgnoreFile(this.ig.default, ignorePath);
             } else {
@@ -187,8 +200,8 @@ class IgnoredPaths {
                         if (packageJSONOptions.eslintIgnore) {
                             if (Array.isArray(packageJSONOptions.eslintIgnore)) {
                                 packageJSONOptions.eslintIgnore.forEach(pattern => {
-                                    addPattern(this.ig.custom, pattern);
-                                    addPattern(this.ig.default, pattern);
+                                    this.addPatternRelativeToIgnoreFile(this.ig.custom, pattern);
+                                    this.addPatternRelativeToIgnoreFile(this.ig.default, pattern);
                                 });
                             } else {
                                 throw new TypeError("Package.json eslintIgnore property requires an array of paths");
@@ -202,12 +215,37 @@ class IgnoredPaths {
             }
 
             if (options.ignorePattern) {
-                addPattern(this.ig.custom, options.ignorePattern);
-                addPattern(this.ig.default, options.ignorePattern);
+                this.addPatternRelativeToCwd(this.ig.custom, options.ignorePattern);
+                this.addPatternRelativeToCwd(this.ig.default, options.ignorePattern);
             }
         }
+    }
 
-        this.options = options;
+    /*
+     * If `ignoreFileDir` is a subdirectory of `cwd`, all paths will be normalized to be relative to `cwd`.
+     * Otherwise, all paths will be normalized to be relative to `ignoreFileDir`.
+     * This ensures that the final normalized ignore rule will not contain `..`, which is forbidden in
+     * ignore rules.
+     */
+
+    addPatternRelativeToCwd(ig, pattern) {
+        ig.addPattern(
+            this.getBaseDir() === this.options.cwd
+                ? pattern
+                : relativize(pattern, path.relative(this.ignoreFileDir, this.options.cwd))
+        );
+    }
+
+    addPatternRelativeToIgnoreFile(ig, pattern) {
+        ig.addPattern(
+            this.getBaseDir() === this.ignoreFileDir
+                ? pattern
+                : relativize(pattern, path.relative(this.options.cwd, this.ignoreFileDir))
+        );
+    }
+
+    getBaseDir() {
+        return this.ignoreFileDir.startsWith(this.options.cwd) ? this.options.cwd : this.ignoreFileDir;
     }
 
     /**
@@ -217,7 +255,7 @@ class IgnoredPaths {
      */
     readIgnoreFile(filePath) {
         if (typeof this.cache[filePath] === "undefined") {
-            this.cache[filePath] = fs.readFileSync(filePath, "utf8");
+            this.cache[filePath] = fs.readFileSync(filePath, "utf8").split("\n").filter(Boolean);
         }
         return this.cache[filePath];
     }
@@ -226,11 +264,13 @@ class IgnoredPaths {
      * add ignore file to node-ignore instance
      * @param {Object} ig, instance of node-ignore
      * @param {string} filePath, file to add to ig
-     * @returns {array} raw ignore rules
+     * @returns {void}
      */
     addIgnoreFile(ig, filePath) {
         ig.ignoreFiles.push(filePath);
-        return ig.add(this.readIgnoreFile(filePath));
+        this
+            .readIgnoreFile(filePath)
+            .forEach(ignoreRule => this.addPatternRelativeToIgnoreFile(ig, ignoreRule));
     }
 
     /**
@@ -243,7 +283,7 @@ class IgnoredPaths {
 
         let result = false;
         const absolutePath = path.resolve(this.options.cwd, filepath);
-        const relativePath = pathUtil.getRelativePath(absolutePath, this.baseDir);
+        const relativePath = pathUtil.getRelativePath(absolutePath, this.getBaseDir());
 
         if (typeof category === "undefined") {
             result = (this.ig.default.filter([relativePath]).length === 0) ||
@@ -261,8 +301,10 @@ class IgnoredPaths {
      * @returns {function()} method to check whether a folder should be ignored by glob.
      */
     getIgnoredFoldersGlobChecker() {
+        const baseDir = this.getBaseDir();
+        const ig = ignore();
 
-        const ig = ignore().add(DEFAULT_IGNORE_DIRS);
+        DEFAULT_IGNORE_DIRS.forEach(ignoreDir => this.addPatternRelativeToCwd(ig, ignoreDir));
 
         if (this.options.dotfiles !== true) {
 
@@ -276,10 +318,8 @@ class IgnoredPaths {
 
         const filter = ig.createFilter();
 
-        const base = this.baseDir;
-
         return function(absolutePath) {
-            const relative = pathUtil.getRelativePath(absolutePath, base);
+            const relative = pathUtil.getRelativePath(absolutePath, baseDir);
 
             if (!relative) {
                 return false;
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index a08b4d7e5..d3793bbbb 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -1,5 +1,5 @@
 /**
- * @fileoverview This option sets a specific tab width for your code
+ * @fileoverview This rule sets a specific indentation style and width for your code
  *
  * @author Teddy Katz
  * @author Vitaly Puzrin
@@ -855,7 +855,11 @@ module.exports = {
                         previousElement &&
                         previousElementLastToken.loc.end.line - countTrailingLinebreaks(previousElementLastToken.value) > startToken.loc.end.line
                     ) {
-                        offsets.setDesiredOffsets(element.range, firstTokenOfPreviousElement, 0);
+                        offsets.setDesiredOffsets(
+                            [previousElement.range[1], element.range[1]],
+                            firstTokenOfPreviousElement,
+                            0
+                        );
                     }
                 }
             });
@@ -997,6 +1001,31 @@ module.exports = {
             return !node || node.loc.start.line === token.loc.start.line;
         }
 
+        /**
+         * Check whether there are any blank (whitespace-only) lines between
+         * two tokens on separate lines.
+         * @param {Token} firstToken The first token.
+         * @param {Token} secondToken The second token.
+         * @returns {boolean} `true` if the tokens are on separate lines and
+         *   there exists a blank line between them, `false` otherwise.
+         */
+        function hasBlankLinesBetween(firstToken, secondToken) {
+            const firstTokenLine = firstToken.loc.end.line;
+            const secondTokenLine = secondToken.loc.start.line;
+
+            if (firstTokenLine === secondTokenLine || firstTokenLine === secondTokenLine - 1) {
+                return false;
+            }
+
+            for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) {
+                if (!tokenInfo.firstTokensByLineNumber.has(line)) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
         const ignoredNodeFirstTokens = new Set();
 
         const baseOffsetListeners = {
@@ -1536,10 +1565,13 @@ module.exports = {
                             const tokenBefore = precedingTokens.get(firstTokenOfLine);
                             const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0];
 
+                            const mayAlignWithBefore = tokenBefore && !hasBlankLinesBetween(tokenBefore, firstTokenOfLine);
+                            const mayAlignWithAfter = tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter);
+
                             // If a comment matches the expected indentation of the token immediately before or after, don't report it.
                             if (
-                                tokenBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
-                                tokenAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
+                                mayAlignWithBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
+                                mayAlignWithAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
                             ) {
                                 return;
                             }
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index b1208e19e..e5242ff5b 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -540,7 +540,7 @@ module.exports = {
             const length = properties.length,
                 widths = properties.map(getKeyWidth), // Width of keys, including quotes
                 align = alignmentOptions.on; // "value" or "colon"
-            let targetWidth = Math.max.apply(null, widths),
+            let targetWidth = Math.max(...widths),
                 beforeColon, afterColon, mode;
 
             if (alignmentOptions && length > 1) { // When aligning values within a group, use the alignment configuration.
diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js
index 273eb8490..88a388309 100644
--- a/lib/rules/max-len.js
+++ b/lib/rules/max-len.js
@@ -266,7 +266,7 @@ module.exports = {
                 // list of comments to ignore
                 comments = ignoreComments || maxCommentLength || ignoreTrailingComments ? sourceCode.getAllComments() : [];
 
-                // we iterate over comments in parallel with the lines
+            // we iterate over comments in parallel with the lines
             let commentsIndex = 0;
 
             const strings = getAllStrings();
diff --git a/lib/rules/no-async-promise-executor.js b/lib/rules/no-async-promise-executor.js
new file mode 100644
index 000000000..55d489eb6
--- /dev/null
+++ b/lib/rules/no-async-promise-executor.js
@@ -0,0 +1,33 @@
+/**
+ * @fileoverview disallow using an async function as a Promise executor
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow using an async function as a Promise executor",
+            category: "Possible Errors",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/no-async-promise-executor"
+        },
+        fixable: null,
+        schema: []
+    },
+
+    create(context) {
+        return {
+            "NewExpression[callee.name='Promise'][arguments.0.async=true]"(node) {
+                context.report({
+                    node: context.getSourceCode().getFirstToken(node.arguments[0], token => token.value === "async"),
+                    message: "Promise executor functions should not be async."
+                });
+            }
+        };
+    }
+};
diff --git a/lib/rules/no-unmodified-loop-condition.js b/lib/rules/no-unmodified-loop-condition.js
index 623144dc0..a02366bad 100644
--- a/lib/rules/no-unmodified-loop-condition.js
+++ b/lib/rules/no-unmodified-loop-condition.js
@@ -16,7 +16,6 @@ const Traverser = require("../util/traverser"),
 // Helpers
 //------------------------------------------------------------------------------
 
-const pushAll = Function.apply.bind(Array.prototype.push);
 const SENTINEL_PATTERN = /(?:(?:Call|Class|Function|Member|New|Yield)Expression|Statement|Declaration)$/;
 const LOOP_PATTERN = /^(?:DoWhile|For|While)Statement$/; // for-in/of statements don't have `test` property.
 const GROUP_PATTERN = /^(?:BinaryExpression|ConditionalExpression)$/;
@@ -356,7 +355,7 @@ module.exports = {
                 let scope;
 
                 while ((scope = queue.pop())) {
-                    pushAll(queue, scope.childScopes);
+                    queue.push(...scope.childScopes);
                     scope.variables.forEach(checkReferences);
                 }
 
diff --git a/lib/rules/no-useless-return.js b/lib/rules/no-useless-return.js
index d801c0e46..731ca4dff 100644
--- a/lib/rules/no-useless-return.js
+++ b/lib/rules/no-useless-return.js
@@ -15,15 +15,6 @@ const astUtils = require("../ast-utils"),
 // Helpers
 //------------------------------------------------------------------------------
 
-/**
- * Adds all elements of 2nd argument into 1st argument.
- *
- * @param {Array} array - The destination array to add.
- * @param {Array} elements - The source array to add.
- * @returns {void}
- */
-const pushAll = Function.apply.bind(Array.prototype.push);
-
 /**
  * Removes the given element from the array.
  *
@@ -137,7 +128,7 @@ module.exports = {
                     continue;
                 }
 
-                pushAll(uselessReturns, segmentInfoMap.get(segment).uselessReturns);
+                uselessReturns.push(...segmentInfoMap.get(segment).uselessReturns);
             }
 
             return uselessReturns;
diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js
index a784e2e31..a70c98554 100644
--- a/lib/rules/prefer-const.js
+++ b/lib/rules/prefer-const.js
@@ -15,15 +15,6 @@ const PATTERN_TYPE = /^(?:.+?Pattern|RestElement|SpreadProperty|ExperimentalRest
 const DECLARATION_HOST_TYPE = /^(?:Program|BlockStatement|SwitchCase)$/;
 const DESTRUCTURING_HOST_TYPE = /^(?:VariableDeclarator|AssignmentExpression)$/;
 
-/**
- * Adds multiple items to the tail of an array.
- *
- * @param {any[]} array - A destination to add.
- * @param {any[]} values - Items to be added.
- * @returns {void}
- */
-const pushAll = Function.apply.bind(Array.prototype.push);
-
 /**
  * Checks whether a given node is located at `ForStatement.init` or not.
  *
@@ -364,7 +355,7 @@ module.exports = {
 
             VariableDeclaration(node) {
                 if (node.kind === "let" && !isInitOfForStatement(node)) {
-                    pushAll(variables, context.getDeclaredVariables(node));
+                    variables.push(...context.getDeclaredVariables(node));
                 }
             }
         };
diff --git a/lib/rules/prefer-object-spread.js b/lib/rules/prefer-object-spread.js
index 6ddb66540..3df16b7ae 100644
--- a/lib/rules/prefer-object-spread.js
+++ b/lib/rules/prefer-object-spread.js
@@ -180,8 +180,14 @@ function defineFixer(node, sourceCode) {
                 for (const innerParen of innerParens) {
                     yield fixer.remove(innerParen);
                 }
-                yield fixer.removeRange([left.range[0], getEndWithSpaces(left, sourceCode)]);
-                yield fixer.removeRange([getStartWithSpaces(right, sourceCode), right.range[1]]);
+                const leftRange = [left.range[0], getEndWithSpaces(left, sourceCode)];
+                const rightRange = [
+                    Math.max(getStartWithSpaces(right, sourceCode), leftRange[1]), // Ensure ranges don't overlap
+                    right.range[1]
+                ];
+
+                yield fixer.removeRange(leftRange);
+                yield fixer.removeRange(rightRange);
 
                 // Remove the comma of this argument if it's duplication.
                 if (
diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js
new file mode 100644
index 000000000..2ee5ad21e
--- /dev/null
+++ b/lib/rules/require-atomic-updates.js
@@ -0,0 +1,239 @@
+/**
+ * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield`
+ * @author Teddy Katz
+ */
+"use strict";
+
+const astUtils = require("../ast-utils");
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+    meta: {
+        docs: {
+            description: "disallow assignments that can lead to race conditions due to usage of `await` or `yield`",
+            category: "Possible Errors",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/require-atomic-updates"
+        },
+        fixable: null,
+        schema: [],
+        messages: {
+            nonAtomicUpdate: "Possible race condition: `{{value}}` might be reassigned based on an outdated value of `{{value}}`."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.getSourceCode();
+        const identifierToSurroundingFunctionMap = new WeakMap();
+        const expressionsByCodePathSegment = new Map();
+
+        //----------------------------------------------------------------------
+        // Helpers
+        //----------------------------------------------------------------------
+
+        const resolvedVariableCache = new WeakMap();
+
+        /**
+         * Gets the variable scope around this variable reference
+         * @param {ASTNode} identifier An `Identifier` AST node
+         * @returns {Scope|null} An escope Scope
+         */
+        function getScope(identifier) {
+            for (let currentNode = identifier; currentNode; currentNode = currentNode.parent) {
+                const scope = sourceCode.scopeManager.acquire(currentNode, true);
+
+                if (scope) {
+                    return scope;
+                }
+            }
+            return null;
+        }
+
+        /**
+         * Resolves a given identifier to a given scope
+         * @param {ASTNode} identifier An `Identifier` AST node
+         * @param {Scope} scope An escope Scope
+         * @returns {Variable|null} An escope Variable corresponding to the given identifier
+         */
+        function resolveVariableInScope(identifier, scope) {
+            return scope.variables.find(variable => variable.name === identifier.name) ||
+                (scope.upper ? resolveVariableInScope(identifier, scope.upper) : null);
+        }
+
+        /**
+         * Resolves an identifier to a variable
+         * @param {ASTNode} identifier An identifier node
+         * @returns {Variable|null} The escope Variable that uses this identifier
+         */
+        function resolveVariable(identifier) {
+            if (!resolvedVariableCache.has(identifier)) {
+                const surroundingScope = getScope(identifier);
+
+                if (surroundingScope) {
+                    resolvedVariableCache.set(identifier, resolveVariableInScope(identifier, surroundingScope));
+                } else {
+                    resolvedVariableCache.set(identifier, null);
+                }
+            }
+
+            return resolvedVariableCache.get(identifier);
+        }
+
+        /**
+         * Checks if an expression is a variable that can only be observed within the given function.
+         * @param {ASTNode} expression The expression to check
+         * @param {ASTNode} surroundingFunction The function node
+         * @returns {boolean} `true` if the expression is a variable which is local to the given function, and is never
+         * referenced in a closure.
+         */
+        function isLocalVariableWithoutEscape(expression, surroundingFunction) {
+            if (expression.type !== "Identifier") {
+                return false;
+            }
+
+            const variable = resolveVariable(expression);
+
+            if (!variable) {
+                return false;
+            }
+
+            return variable.references.every(reference => identifierToSurroundingFunctionMap.get(reference.identifier) === surroundingFunction) &&
+                variable.defs.every(def => identifierToSurroundingFunctionMap.get(def.name) === surroundingFunction);
+        }
+
+        /**
+         * Reports an AssignmentExpression node that has a non-atomic update
+         * @param {ASTNode} assignmentExpression The assignment that is potentially unsafe
+         * @returns {void}
+         */
+        function reportAssignment(assignmentExpression) {
+            context.report({
+                node: assignmentExpression,
+                messageId: "nonAtomicUpdate",
+                data: {
+                    value: sourceCode.getText(assignmentExpression.left)
+                }
+            });
+        }
+
+        /**
+         * If the control flow graph of a function enters an assignment expression, then does the
+         * both of the following steps in order (possibly with other steps in between) before exiting the
+         * assignment expression, then the assignment might be using an outdated value.
+         * 1. Enters a read of the variable or property assigned in the expression (not necessary if operator assignment is used)
+         * 2. Exits an `await` or `yield` expression
+         *
+         * This function checks for the outdated values and reports them.
+         * @param {CodePathSegment} codePathSegment The current code path segment to traverse
+         * @param {ASTNode} surroundingFunction The function node containing the code path segment
+         * @returns {void}
+         */
+        function findOutdatedReads(
+            codePathSegment,
+            surroundingFunction,
+            {
+                seenSegments = new Set(),
+                openAssignmentsWithoutReads = new Set(),
+                openAssignmentsWithReads = new Set()
+            } = {}
+        ) {
+            if (seenSegments.has(codePathSegment)) {
+
+                // An AssignmentExpression can't contain loops, so it's not necessary to reenter them with new state.
+                return;
+            }
+
+            expressionsByCodePathSegment.get(codePathSegment).forEach(({ entering, node }) => {
+                if (node.type === "AssignmentExpression") {
+                    if (entering) {
+                        (node.operator === "=" ? openAssignmentsWithoutReads : openAssignmentsWithReads).add(node);
+                    } else {
+                        openAssignmentsWithoutReads.delete(node);
+                        openAssignmentsWithReads.delete(node);
+                    }
+                } else if (!entering && (node.type === "AwaitExpression" || node.type === "YieldExpression")) {
+                    [...openAssignmentsWithReads]
+                        .filter(assignment => !isLocalVariableWithoutEscape(assignment.left, surroundingFunction))
+                        .forEach(reportAssignment);
+
+                    openAssignmentsWithReads.clear();
+                } else if (!entering && (node.type === "Identifier" || node.type === "MemberExpression")) {
+                    [...openAssignmentsWithoutReads]
+                        .filter(assignment => (
+                            assignment.left !== node &&
+                            assignment.left.type === node.type &&
+                            astUtils.equalTokens(assignment.left, node, sourceCode)
+                        ))
+                        .forEach(assignment => {
+                            openAssignmentsWithoutReads.delete(assignment);
+                            openAssignmentsWithReads.add(assignment);
+                        });
+                }
+            });
+
+            codePathSegment.nextSegments.forEach(nextSegment => {
+                findOutdatedReads(
+                    nextSegment,
+                    surroundingFunction,
+                    {
+                        seenSegments: new Set(seenSegments).add(codePathSegment),
+                        openAssignmentsWithoutReads: new Set(openAssignmentsWithoutReads),
+                        openAssignmentsWithReads: new Set(openAssignmentsWithReads)
+                    }
+                );
+            });
+        }
+
+        //----------------------------------------------------------------------
+        // Public
+        //----------------------------------------------------------------------
+
+        const currentCodePathSegmentStack = [];
+        let currentCodePathSegment = null;
+        const functionStack = [];
+
+        return {
+            onCodePathStart() {
+                currentCodePathSegmentStack.push(currentCodePathSegment);
+            },
+
+            onCodePathEnd(codePath, node) {
+                currentCodePathSegment = currentCodePathSegmentStack.pop();
+
+                if (astUtils.isFunction(node) && (node.async || node.generator)) {
+                    findOutdatedReads(codePath.initialSegment, node);
+                }
+            },
+
+            onCodePathSegmentStart(segment) {
+                currentCodePathSegment = segment;
+                expressionsByCodePathSegment.set(segment, []);
+            },
+
+            "AssignmentExpression, Identifier, MemberExpression, AwaitExpression, YieldExpression"(node) {
+                expressionsByCodePathSegment.get(currentCodePathSegment).push({ entering: true, node });
+            },
+
+            "AssignmentExpression, Identifier, MemberExpression, AwaitExpression, YieldExpression:exit"(node) {
+                expressionsByCodePathSegment.get(currentCodePathSegment).push({ entering: false, node });
+            },
+
+            ":function"(node) {
+                functionStack.push(node);
+            },
+
+            ":function:exit"() {
+                functionStack.pop();
+            },
+
+            Identifier(node) {
+                if (functionStack.length) {
+                    identifierToSurroundingFunctionMap.set(node, functionStack[functionStack.length - 1]);
+                }
+            }
+        };
+    }
+};
diff --git a/lib/util/node-event-generator.js b/lib/util/node-event-generator.js
index 9d477bbb4..1ae566431 100644
--- a/lib/util/node-event-generator.js
+++ b/lib/util/node-event-generator.js
@@ -45,8 +45,8 @@ function getPossibleTypes(parsedSelector) {
         case "matches": {
             const typesForComponents = parsedSelector.selectors.map(getPossibleTypes);
 
-            if (typesForComponents.every(typesForComponent => typesForComponent)) {
-                return lodash.union.apply(null, typesForComponents);
+            if (typesForComponents.every(Boolean)) {
+                return lodash.union(...typesForComponents);
             }
             return null;
         }
@@ -63,7 +63,7 @@ function getPossibleTypes(parsedSelector) {
              * If at least one of the components could only match a particular type, the compound could only match
              * the intersection of those types.
              */
-            return lodash.intersection.apply(null, typesForComponents);
+            return lodash.intersection(...typesForComponents);
         }
 
         case "child":
diff --git a/packages/eslint-config-eslint/default.yml b/packages/eslint-config-eslint/default.yml
index da4faf6e5..127e7d7d7 100644
--- a/packages/eslint-config-eslint/default.yml
+++ b/packages/eslint-config-eslint/default.yml
@@ -1,10 +1,6 @@
 extends:
     - "eslint:recommended"
     - "plugin:node/recommended"
-
-plugins:
-    - "node"
-
 rules:
     array-bracket-spacing: "error"
     array-callback-return: "error"
@@ -54,6 +50,7 @@ rules:
     new-parens: "error"
     no-alert: "error"
     no-array-constructor: "error"
+    no-async-promise-executor: "error"
     no-buffer-constructor: "error"
     no-caller: "error"
     no-catch-shadow: "error"
@@ -160,6 +157,7 @@ rules:
     quotes: ["error", "double"]
     quote-props: ["error", "as-needed"]
     radix: "error"
+    require-atomic-updates: "error"
     require-jsdoc: "error"
     rest-spread-spacing: "error"
     semi: "error"


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index a02f0b9be..d7e4c2bfa 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -140,7 +140,7 @@ const IT = Symbol("it");
  */
 function itDefaultHandler(text, method) {
     try {
-        return method.apply(this);
+        return method.call(this);
     } catch (err) {
         if (err instanceof assert.AssertionError) {
             err.message += ` (${util.inspect(err.actual)} ${err.operator} ${util.inspect(err.expected)})`;
@@ -157,7 +157,7 @@ function itDefaultHandler(text, method) {
  * @returns {any} Returned value of `method`.
  */
 function describeDefaultHandler(text, method) {
-    return method.apply(this);
+    return method.call(this);
 }
 
 class RuleTester {
diff --git a/tests/fixtures/ignored-paths/subdir/.eslintignoreInChildDir b/tests/fixtures/ignored-paths/subdir/.eslintignoreInChildDir
new file mode 100644
index 000000000..8c7a20132
--- /dev/null
+++ b/tests/fixtures/ignored-paths/subdir/.eslintignoreInChildDir
@@ -0,0 +1,5 @@
+/undef.js
+foo.js
+*.txt
+!/bar.txt
+!baz.txt
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index 010488931..dfcf310e5 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -123,7 +123,7 @@ describe("IgnoredPaths", () => {
             const ignoredPaths = new IgnoredPaths({ ignore: true, cwd: getFixturePath() });
             const ignorePatterns = getIgnorePatterns(ignoredPaths);
 
-            assert.isNotNull(ignoredPaths.baseDir);
+            assert.isNotNull(ignoredPaths.ignoreFileDir);
             assert.deepStrictEqual(getIgnoreFiles(ignoredPaths), [expectedIgnoreFile]);
             assert.include(ignorePatterns, "sampleignorepattern");
         });
@@ -131,7 +131,7 @@ describe("IgnoredPaths", () => {
         it("should set baseDir to cwd when no ignore file was loaded", () => {
             const ignoredPaths = new IgnoredPaths({ cwd: getFixturePath("no-ignore-file") });
 
-            assert.strictEqual(ignoredPaths.baseDir, getFixturePath("no-ignore-file"));
+            assert.strictEqual(ignoredPaths.ignoreFileDir, getFixturePath("no-ignore-file"));
         });
 
         it("should not travel to parent directories to find .eslintignore when it's missing and cwd is provided", () => {
@@ -241,7 +241,7 @@ describe("IgnoredPaths", () => {
         it("should set baseDir to directory containing ignorePath if provided", () => {
             const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: ignoreFilePath, cwd: getFixturePath() });
 
-            assert.strictEqual(ignoredPaths.baseDir, path.dirname(ignoreFilePath));
+            assert.strictEqual(ignoredPaths.ignoreFileDir, path.dirname(ignoreFilePath));
         });
 
     });
@@ -401,6 +401,40 @@ describe("IgnoredPaths", () => {
             assert.isFalse(ignoredPaths.contains(getFixturePath("subdir/undef.js")));
             assert.isTrue(ignoredPaths.contains(getFixturePath("undef.js")));
         });
+
+        it("should resolve relative paths from the ignorePath when it's in a child directory", () => {
+            const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath("subdir/.eslintignoreInChildDir"), cwd: getFixturePath() });
+
+            assert.isTrue(ignoredPaths.contains(getFixturePath("subdir/undef.js")));
+            assert.isFalse(ignoredPaths.contains(getFixturePath("undef.js")));
+            assert.isTrue(ignoredPaths.contains(getFixturePath("foo.js")));
+            assert.isTrue(ignoredPaths.contains(getFixturePath("subdir/foo.js")));
+
+            assert.isTrue(ignoredPaths.contains(getFixturePath("node_modules/bar.js")));
+        });
+
+        it("should resolve relative paths from the ignorePath when it contains negated globs", () => {
+            const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath("subdir/.eslintignoreInChildDir"), cwd: getFixturePath() });
+
+            assert.isTrue(ignoredPaths.contains("subdir/blah.txt"));
+            assert.isTrue(ignoredPaths.contains("blah.txt"));
+            assert.isFalse(ignoredPaths.contains("subdir/bar.txt"));
+            assert.isTrue(ignoredPaths.contains("bar.txt"));
+            assert.isFalse(ignoredPaths.contains("subdir/baz.txt"));
+            assert.isFalse(ignoredPaths.contains("baz.txt"));
+        });
+
+        it("should resolve default ignore patterns from the CWD even when the ignorePath is in a subdirectory", () => {
+            const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath("subdir/.eslintignoreInChildDir"), cwd: getFixturePath() });
+
+            assert.isTrue(ignoredPaths.contains("node_modules/blah.js"));
+        });
+
+        it("should resolve default ignore patterns from the CWD even when the ignorePath is in a parent directory", () => {
+            const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath(".eslintignoreForDifferentCwd"), cwd: getFixturePath("subdir") });
+
+            assert.isTrue(ignoredPaths.contains("node_modules/blah.js"));
+        });
     });
 
     describe("initialization with ignorePath containing commented lines", () => {
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index ab8ba4c18..b25e25949 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -64,7 +64,7 @@ function unIndent(strings) {
     const templateValue = strings[0];
     const lines = templateValue.replace(/^\n/, "").replace(/\n\s*$/, "").split("\n");
     const lineIndents = lines.filter(line => line.trim()).map(line => line.match(/ */)[0].length);
-    const minLineIndent = Math.min.apply(null, lineIndents);
+    const minLineIndent = Math.min(...lineIndents);
 
     return lines.map(line => line.slice(minLineIndent)).join("\n");
 }
@@ -4843,6 +4843,56 @@ ruleTester.run("indent", rule, {
                         2
                 }
             }
+        `,
+
+        //----------------------------------------------------------------------
+        // Comment alignment tests
+        //----------------------------------------------------------------------
+        unIndent`
+            if (foo) {
+            // Comment can align with code immediately above even if "incorrect" alignment
+                doSomething();
+            }
+        `,
+        unIndent`
+            if (foo) {
+                doSomething();
+            // Comment can align with code immediately below even if "incorrect" alignment
+            }
+        `,
+        unIndent`
+            if (foo) {
+                // Comment can be in correct alignment even if not aligned with code above/below
+            }
+        `,
+        unIndent`
+            if (foo) {
+
+                // Comment can be in correct alignment even if gaps between (and not aligned with) code above/below
+
+            }
+        `,
+        unIndent`
+            [{
+                foo
+            },
+
+            // Comment between nodes
+
+            {
+                bar
+            }];
+        `,
+        unIndent`
+            [{
+                foo
+            },
+
+            // Comment between nodes
+
+            { // comment
+                bar
+            }];
         `
     ],
 
@@ -9383,6 +9433,71 @@ ruleTester.run("indent", rule, {
                 }
             `,
             errors: expectedErrors([4, 12, 8, "Numeric"])
+        },
+
+        //----------------------------------------------------------------------
+        // Comment alignment tests
+        //----------------------------------------------------------------------
+        {
+            code: unIndent`
+                if (foo) {
+
+                // Comment cannot align with code immediately above if there is a whitespace gap
+                    doSomething();
+                }
+            `,
+            output: unIndent`
+                if (foo) {
+
+                    // Comment cannot align with code immediately above if there is a whitespace gap
+                    doSomething();
+                }
+            `,
+            errors: expectedErrors([3, 4, 0, "Line"])
+        },
+        {
+            code: unIndent`
+                if (foo) {
+                    foo(
+                        bar);
+                // Comment cannot align with code immediately below if there is a whitespace gap
+
+                }
+            `,
+            output: unIndent`
+                if (foo) {
+                    foo(
+                        bar);
+                    // Comment cannot align with code immediately below if there is a whitespace gap
+
+                }
+            `,
+            errors: expectedErrors([4, 4, 0, "Line"])
+        },
+        {
+            code: unIndent`
+                [{
+                    foo
+                },
+
+                    // Comment between nodes
+
+                {
+                    bar
+                }];
+            `,
+            output: unIndent`
+                [{
+                    foo
+                },
+
+                // Comment between nodes
+
+                {
+                    bar
+                }];
+            `,
+            errors: expectedErrors([5, 0, 4, "Line"])
         }
     ]
 });
diff --git a/tests/lib/rules/no-async-promise-executor.js b/tests/lib/rules/no-async-promise-executor.js
new file mode 100644
index 000000000..d5bcd0ae9
--- /dev/null
+++ b/tests/lib/rules/no-async-promise-executor.js
@@ -0,0 +1,61 @@
+/**
+ * @fileoverview disallow using an async function as a Promise executor
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/no-async-promise-executor");
+const RuleTester = require("../../../lib/testers/rule-tester");
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+ruleTester.run("no-async-promise-executor", rule, {
+
+    valid: [
+        "new Promise((resolve, reject) => {})",
+        "new Promise((resolve, reject) => {}, async function unrelated() {})",
+        "new Foo(async (resolve, reject) => {})"
+    ],
+
+    invalid: [
+        {
+            code: "new Promise(async function foo(resolve, reject) {})",
+            errors: [{
+                message: "Promise executor functions should not be async.",
+                line: 1,
+                column: 13,
+                endLine: 1,
+                endColumn: 18
+            }]
+        },
+        {
+            code: "new Promise(async (resolve, reject) => {})",
+            errors: [{
+                message: "Promise executor functions should not be async.",
+                line: 1,
+                column: 13,
+                endLine: 1,
+                endColumn: 18
+            }]
+        },
+        {
+            code: "new Promise(((((async () => {})))))",
+            errors: [{
+                message: "Promise executor functions should not be async.",
+                line: 1,
+                column: 17,
+                endLine: 1,
+                endColumn: 22
+            }]
+        }
+    ]
+});
diff --git a/tests/lib/rules/no-invalid-this.js b/tests/lib/rules/no-invalid-this.js
index a87a9aa44..f1d11030c 100644
--- a/tests/lib/rules/no-invalid-this.js
+++ b/tests/lib/rules/no-invalid-this.js
@@ -83,7 +83,7 @@ function extractPatterns(patterns, type) {
     }));
 
     // Flatten.
-    return Array.prototype.concat.apply([], patternsList);
+    return [].concat(...patternsList);
 }
 
 
diff --git a/tests/lib/rules/prefer-object-spread.js b/tests/lib/rules/prefer-object-spread.js
index 7790f8796..acac665e5 100644
--- a/tests/lib/rules/prefer-object-spread.js
+++ b/tests/lib/rules/prefer-object-spread.js
@@ -808,6 +808,32 @@ ruleTester.run("prefer-object-spread", rule, {
                     column: 29
                 }
             ]
+        },
+
+        // https://github.com/eslint/eslint/issues/10646
+        {
+            code: "Object.assign({ });",
+            output: "({});",
+            errors: [
+                {
+                    messageId: "useLiteralMessage",
+                    type: "CallExpression",
+                    line: 1,
+                    column: 1
+                }
+            ]
+        },
+        {
+            code: "Object.assign({\n});",
+            output: "({});",
+            errors: [
+                {
+                    messageId: "useLiteralMessage",
+                    type: "CallExpression",
+                    line: 1,
+                    column: 1
+                }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/require-atomic-updates.js b/tests/lib/rules/require-atomic-updates.js
new file mode 100644
index 000000000..eabd52149
--- /dev/null
+++ b/tests/lib/rules/require-atomic-updates.js
@@ -0,0 +1,143 @@
+/**
+ * @fileoverview disallow assignments that can lead to race conditions due to usage of `await` or `yield`
+ * @author Teddy Katz
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const rule = require("../../../lib/rules/require-atomic-updates");
+const RuleTester = require("../../../lib/testers/rule-tester");
+
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } });
+
+const VARIABLE_ERROR = {
+    messageId: "nonAtomicUpdate",
+    data: { value: "foo" },
+    type: "AssignmentExpression"
+};
+
+const STATIC_PROPERTY_ERROR = {
+    messageId: "nonAtomicUpdate",
+    data: { value: "foo.bar" },
+    type: "AssignmentExpression"
+};
+
+const COMPUTED_PROPERTY_ERROR = {
+    messageId: "nonAtomicUpdate",
+    data: { value: "foo[bar].baz" },
+    type: "AssignmentExpression"
+};
+
+ruleTester.run("require-atomic-updates", rule, {
+
+    valid: [
+        "let foo; async function x() { foo += bar; }",
+        "let foo; async function x() { foo = foo + bar; }",
+        "let foo; async function x() { foo = await bar + foo; }",
+        "async function x() { let foo; foo += await bar; }",
+        "let foo; async function x() { foo = (await result)(foo); }",
+        "let foo; async function x() { foo = bar(await something, foo) }",
+        "function* x() { let foo; foo += yield bar; }",
+        "const foo = {}; async function x() { foo.bar = await baz; }",
+        "const foo = []; async function x() { foo[x] += 1;  }",
+        "let foo; function* x() { foo = bar + foo; }",
+        "async function x() { let foo; bar(() => baz += 1); foo += await amount; }",
+        "let foo; async function x() { foo = condition ? foo : await bar; }",
+        "async function x() { let foo; bar(() => { let foo; blah(foo); }); foo += await result; }"
+    ],
+
+    invalid: [
+        {
+            code: "let foo; async function x() { foo += await amount; }",
+            errors: [{ messageId: "nonAtomicUpdate", data: { value: "foo" } }]
+        },
+        {
+            code: "let foo; async function x() { while (condition) { foo += await amount; } }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = foo + await amount; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = foo + (bar ? baz : await amount); }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = foo + (bar ? await amount : baz); }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = condition ? foo + await amount : somethingElse; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = (condition ? foo : await bar) + await bar; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo += bar + await amount; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "async function x() { let foo; bar(() => foo); foo += await amount; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; function* x() { foo += yield baz }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = bar(foo, await something) }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "const foo = {}; async function x() { foo.bar += await baz }",
+            errors: [STATIC_PROPERTY_ERROR]
+        },
+        {
+            code: "const foo = []; async function x() { foo[bar].baz += await result;  }",
+            errors: [COMPUTED_PROPERTY_ERROR]
+        },
+        {
+            code: "let foo; async function* x() { foo = (yield foo) + await bar; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = foo + await result(foo); }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = await result(foo, await somethingElse); }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "function* x() { let foo; yield async function y() { foo += await bar; } }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function* x() { foo = await foo + (yield bar); }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo; async function x() { foo = bar + await foo; }",
+            errors: [VARIABLE_ERROR]
+        },
+        {
+            code: "let foo = {}; async function x() { foo[bar].baz = await (foo.bar += await foo[bar].baz) }",
+            errors: [COMPUTED_PROPERTY_ERROR, STATIC_PROPERTY_ERROR]
+        },
+        {
+            code: "async function x() { foo += await bar; }",
+            errors: [VARIABLE_ERROR]
+        }
+    ]
+});
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index 4265ab1b9..4a8608906 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -44,12 +44,12 @@ const ruleTesterTestEmitter = new EventEmitter();
 
 RuleTester.describe = function(text, method) {
     ruleTesterTestEmitter.emit("describe", text, method);
-    return method.apply(this);
+    return method.call(this);
 };
 
 RuleTester.it = function(text, method) {
     ruleTesterTestEmitter.emit("it", text, method);
-    return method.apply(this);
+    return method.call(this);
 };
 
 //------------------------------------------------------------------------------