Repository: eslint/eslint (js)

Issue:
[require-atomic-updates]: False positive on reused loop variable name
**Tell us about your environment**

<!--
    If you are using ESLint v6.5.0 or later, you can run ESLint with the `--env-info` flag and paste the output here.
-->

Environment Info:

Node version: v14.15.4
npm version: v7.5.3
Local ESLint version: v7.21.0 (Currently used)
Global ESLint version: Not found

Parser: `@typescript-eslint/parser`

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
module.exports = {
  env: {
    es2020: true,
    node: true
  },
  root: true,
  parser: '@typescript-eslint/parser',
  parserOptions: {
    'project': './tsconfig.eslint.json',
    'sourceType': 'module',
    'tsconfigRootDir': __dirname,
  },
  plugins: [
    'eslint-plugin-import',
    '@typescript-eslint',
  ],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:@typescript-eslint/recommended-requiring-type-checking',
  ],
  rules: {
    '@typescript-eslint/no-non-null-assertion': 'warn',
    '@typescript-eslint/no-shadow': ['error', { 'hoist': 'all' }],
    '@typescript-eslint/no-unused-expressions': 'error',
    '@typescript-eslint/require-await': 'warn',
    '@typescript-eslint/require-array-sort-compare': 'error',
    '@typescript-eslint/no-base-to-string': 'error',
    '@typescript-eslint/restrict-plus-operands': 'error',
    'array-callback-return': 'error',
    'eqeqeq': ['error', 'always'],
    'guard-for-in': 'error',
    'no-bitwise': 'error',
    'no-caller': 'error',
    'no-eval': 'error',
    'no-new-wrappers': 'error',
    'no-template-curly-in-string': 'error',
    'no-throw-literal': 'error',
    'no-undef-init': 'error',
    'no-unreachable-loop': 'error',
    'no-useless-backreference': 'error',
    'no-var': 'error',
    'require-atomic-updates': 'error',
    'prefer-const': 'off',      // change to 'error'
    '@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
    '@typescript-eslint/semi': ['error', 'always'],
    '@typescript-eslint/unified-signatures': 'error',
    '@typescript-eslint/prefer-string-starts-ends-with': 'warn',
    '@typescript-eslint/no-extra-semi': 'off',        // change to 'warn'
    '@typescript-eslint/no-inferrable-types': 'off',    // change to ['warn', {ignoreParameters: true}]
    'arrow-body-style': 'off',          // change to ['error', 'as-needed'],
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    '@typescript-eslint/no-unused-vars': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    '@typescript-eslint/no-unsafe-assignment': 'off',
    '@typescript-eslint/no-unsafe-member-access': 'off',
    '@typescript-eslint/no-unsafe-call': 'off',
    '@typescript-eslint/no-unsafe-return': 'off',
    '@typescript-eslint/no-implicit-any-catch': 'off',
    '@typescript-eslint/restrict-template-expressions': 'off',
    '@typescript-eslint/no-unnecessary-condition': 'off',
    'no-inner-declarations': 'off',
    'no-promise-executor-return': 'off',
  }
};
```

</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

<!-- Paste the source code below: -->
```js
async function foo(e) {
}

async function run() {
  const input = [];
  const props = [];

  for(const entry of input) {
    const prop = props.find(a => a.id === entry.id) || null;
    await foo(entry);
  }

  // Possible race condition: `const entry` might be reassigned based on an outdated value of `const entry`
  for(const entry of input) {
    const prop = props.find(a => a.id === entry.id) || null;
  }

  for(const entry2 of input) {
    const prop = props.find(a => a.id === entry2.id) || null;
  }
}
```

<!-- Paste the command you used to run ESLint: -->
Command: `npx eslint scripts/sample-require-atomic-updates.ts`

**What did you expect to happen?**

Did not expect any errors because the `entry` variables in the two loops are independent.

**What actually happened? Please include the actual, raw output from ESLint.**

ESLint seems to think that the variable captured in the second loop's arrow function closure is the `entry` variable from the first loop, and flags an error.

```
/......./scripts/sample-require-atomic-updates.ts
  14:3  error  Possible race condition: `const entry` might be reassigned based on an outdated value of `const entry`  require-atomic-updates

✖ 1 problem (1 error, 0 warnings)
```

Pull request description:
Fix: track variables, not names in require-atomic-updates (fixes #14208)
#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/master/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

[ ] Documentation update
[x] 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:

fixes #14208

Fixes false positives like:
```js
async function foo(e) {
}

async function run() {
  const input = [];
  const props = [];

  for(const entry of input) {
    const prop = props.find(a => a.id === entry.id) || null;
    await foo(entry);
  }

  // false positive:
  // Possible race condition: `const entry` might be reassigned based on an outdated value of `const entry`
  for(const entry of input) {
    const prop = props.find(a => a.id === entry.id) || null;
  }

  for(const entry2 of input) {
    const prop = props.find(a => a.id === entry2.id) || null;
  }
}
```

#### What changes did you make? (Give an overview)

I adapted the `if` condition surrounding the code block that is pushing/setting a reference to the `assignmentReferences` map. This map is subsequently used to get `references` and check for outdated identifier names.

I moved the `isLocalVariableWithoutEscape` in said `if` condition out to a separate variable and extended it with: 
```js
const isVariableDeclaration = isLocalVariableWithoutEscape(variable, isMemberAccess) ||
    node.parent.type === "VariableDeclarator";
```

Since the condition had a comment with  "exclude variable declarations", I removed it and expressed it with the `isVariableDeclaration` variable name.

#### Is there anything you'd like reviewers to focus on?

This is the first time contributing to ESLint (or open-source that is). Please focus on edge-cases or other implications since I do not have the complete context. I'm happy to hear about your concerns and improvement points.


Patch:
diff --git a/README.md b/README.md
index 1aa25ee3d..f53e35ce8 100644
--- a/README.md
+++ b/README.md
@@ -281,7 +281,7 @@ The following companies, organizations, and individuals support ESLint's ongoing
 <!--sponsorsstart-->
 <h3>Platinum Sponsors</h3>
 <p><a href="https://automattic.com"><img src="https://images.opencollective.com/photomatt/d0ef3e1/logo.png" alt="Automattic" height="undefined"></a></p><h3>Gold Sponsors</h3>
-<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.shopify.com"><img src="https://images.opencollective.com/shopify/e780cd4/logo.png" alt="Shopify" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://opensource.microsoft.com"><img src="https://avatars.githubusercontent.com/u/6154722?v=4" alt="Microsoft" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
+<p><a href="https://nx.dev"><img src="https://images.opencollective.com/nx/0efbe42/logo.png" alt="Nx (by Nrwl)" height="96"></a> <a href="https://google.com/chrome"><img src="https://images.opencollective.com/chrome/dc55bd4/logo.png" alt="Chrome's Web Framework & Tools Performance Fund" height="96"></a> <a href="https://www.salesforce.com"><img src="https://images.opencollective.com/salesforce/ca8f997/logo.png" alt="Salesforce" height="96"></a> <a href="https://www.airbnb.com/"><img src="https://images.opencollective.com/airbnb/d327d66/logo.png" alt="Airbnb" height="96"></a> <a href="https://opensource.microsoft.com"><img src="https://avatars.githubusercontent.com/u/6154722?v=4" alt="Microsoft" height="96"></a> <a href="https://substack.com/"><img src="https://avatars.githubusercontent.com/u/53023767?v=4" alt="Substack" height="96"></a></p><h3>Silver Sponsors</h3>
 <p><a href="https://retool.com/"><img src="https://images.opencollective.com/retool/98ea68e/logo.png" alt="Retool" height="64"></a> <a href="https://liftoff.io/"><img src="https://images.opencollective.com/liftoff/5c4fa84/logo.png" alt="Liftoff" height="64"></a></p><h3>Bronze Sponsors</h3>
 <p><a href="https://buy.fineproxy.org/eng/"><img src="https://images.opencollective.com/buy-fineproxy-org/b282e39/logo.png" alt="Buy.Fineproxy.Org" height="32"></a> <a href="https://www.crosswordsolver.org/anagram-solver/"><img src="https://images.opencollective.com/anagram-solver/2666271/logo.png" alt="Anagram Solver" height="32"></a> <a href="null"><img src="https://images.opencollective.com/bugsnag-stability-monitoring/c2cef36/logo.png" alt="Bugsnag Stability Monitoring" height="32"></a> <a href="https://mixpanel.com"><img src="https://images.opencollective.com/mixpanel/cd682f7/logo.png" alt="Mixpanel" height="32"></a> <a href="https://www.vpsserver.com"><img src="https://images.opencollective.com/vpsservercom/logo.png" alt="VPS Server" height="32"></a> <a href="https://icons8.com"><img src="https://images.opencollective.com/icons8/7fa1641/logo.png" alt="Icons8: free icons, photos, illustrations, and music" height="32"></a> <a href="https://discord.com"><img src="https://images.opencollective.com/discordapp/7e3d9a9/logo.png" alt="Discord" height="32"></a> <a href="https://themeisle.com"><img src="https://images.opencollective.com/themeisle/d5592fe/logo.png" alt="ThemeIsle" height="32"></a> <a href="https://www.firesticktricks.com"><img src="https://images.opencollective.com/fire-stick-tricks/b8fbe2c/logo.png" alt="Fire Stick Tricks" height="32"></a></p>
 <!--sponsorsend-->
diff --git a/docs/rules/require-atomic-updates.md b/docs/rules/require-atomic-updates.md
index 593ccdd60..8dd313815 100644
--- a/docs/rules/require-atomic-updates.md
+++ b/docs/rules/require-atomic-updates.md
@@ -84,7 +84,7 @@ async function foo() {
 }
 
 function* bar() {
-  result += yield;
+  result = (yield) + result;
 
   result = (yield somethingElse) + result;
 
diff --git a/lib/init/config-file.js b/lib/init/config-file.js
index fc62b8152..4c648ac05 100644
--- a/lib/init/config-file.js
+++ b/lib/init/config-file.js
@@ -117,6 +117,7 @@ function writeJSConfigFile(config, filePath) {
 function write(config, filePath) {
     switch (path.extname(filePath)) {
         case ".js":
+        case ".cjs":
             writeJSConfigFile(config, filePath);
             break;
 
diff --git a/lib/init/config-initializer.js b/lib/init/config-initializer.js
index 8cc09b6a9..3c7f2ba09 100644
--- a/lib/init/config-initializer.js
+++ b/lib/init/config-initializer.js
@@ -12,6 +12,7 @@
 
 const util = require("util"),
     path = require("path"),
+    fs = require("fs"),
     enquirer = require("enquirer"),
     ProgressBar = require("progress"),
     semver = require("semver"),
@@ -48,6 +49,16 @@ function writeFile(config, format) {
         extname = ".yml";
     } else if (format === "JSON") {
         extname = ".json";
+    } else if (format === "JavaScript") {
+        const pkgJSONPath = npmUtils.findPackageJson();
+
+        if (pkgJSONPath) {
+            const pkgJSONContents = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8"));
+
+            if (pkgJSONContents.type === "module") {
+                extname = ".cjs";
+            }
+        }
     }
 
     const installedESLint = config.installedESLint;
@@ -684,6 +695,7 @@ const init = {
     hasESLintVersionConflict,
     installModules,
     processAnswers,
+    writeFile,
     /* istanbul ignore next */initializeConfig() {
         return promptUser();
     }
diff --git a/lib/init/npm-utils.js b/lib/init/npm-utils.js
index 555ea2b2b..35191cc08 100644
--- a/lib/init/npm-utils.js
+++ b/lib/init/npm-utils.js
@@ -172,6 +172,7 @@ function checkPackageJson(startDir) {
 module.exports = {
     installSyncSaveDev,
     fetchPeerDependencies,
+    findPackageJson,
     checkDeps,
     checkDevDeps,
     checkPackageJson
diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js
index c552f1bd8..b3df90742 100644
--- a/lib/rules/require-atomic-updates.js
+++ b/lib/rules/require-atomic-updates.js
@@ -13,6 +13,10 @@
  */
 function createReferenceMap(scope, outReferenceMap = new Map()) {
     for (const reference of scope.references) {
+        if (reference.resolved === null) {
+            continue;
+        }
+
         outReferenceMap.set(reference.identifier, reference);
     }
     for (const childScope of scope.childScopes) {
@@ -86,42 +90,42 @@ class SegmentInfo {
      * @returns {void}
      */
     initialize(segment) {
-        const outdatedReadVariableNames = new Set();
-        const freshReadVariableNames = new Set();
+        const outdatedReadVariables = new Set();
+        const freshReadVariables = new Set();
 
         for (const prevSegment of segment.prevSegments) {
             const info = this.info.get(prevSegment);
 
             if (info) {
-                info.outdatedReadVariableNames.forEach(Set.prototype.add, outdatedReadVariableNames);
-                info.freshReadVariableNames.forEach(Set.prototype.add, freshReadVariableNames);
+                info.outdatedReadVariables.forEach(Set.prototype.add, outdatedReadVariables);
+                info.freshReadVariables.forEach(Set.prototype.add, freshReadVariables);
             }
         }
 
-        this.info.set(segment, { outdatedReadVariableNames, freshReadVariableNames });
+        this.info.set(segment, { outdatedReadVariables, freshReadVariables });
     }
 
     /**
      * Mark a given variable as read on given segments.
      * @param {PathSegment[]} segments The segments that it read the variable on.
-     * @param {string} variableName The variable name to be read.
+     * @param {Variable} variable The variable to be read.
      * @returns {void}
      */
-    markAsRead(segments, variableName) {
+    markAsRead(segments, variable) {
         for (const segment of segments) {
             const info = this.info.get(segment);
 
             if (info) {
-                info.freshReadVariableNames.add(variableName);
+                info.freshReadVariables.add(variable);
 
                 // If a variable is freshly read again, then it's no more out-dated.
-                info.outdatedReadVariableNames.delete(variableName);
+                info.outdatedReadVariables.delete(variable);
             }
         }
     }
 
     /**
-     * Move `freshReadVariableNames` to `outdatedReadVariableNames`.
+     * Move `freshReadVariables` to `outdatedReadVariables`.
      * @param {PathSegment[]} segments The segments to process.
      * @returns {void}
      */
@@ -130,8 +134,8 @@ class SegmentInfo {
             const info = this.info.get(segment);
 
             if (info) {
-                info.freshReadVariableNames.forEach(Set.prototype.add, info.outdatedReadVariableNames);
-                info.freshReadVariableNames.clear();
+                info.freshReadVariables.forEach(Set.prototype.add, info.outdatedReadVariables);
+                info.freshReadVariables.clear();
             }
         }
     }
@@ -139,14 +143,14 @@ class SegmentInfo {
     /**
      * Check if a given variable is outdated on the current segments.
      * @param {PathSegment[]} segments The current segments.
-     * @param {string} variableName The variable name to check.
+     * @param {Variable} variable The variable to check.
      * @returns {boolean} `true` if the variable is outdated on the segments.
      */
-    isOutdated(segments, variableName) {
+    isOutdated(segments, variable) {
         for (const segment of segments) {
             const info = this.info.get(segment);
 
-            if (info && info.outdatedReadVariableNames.has(variableName)) {
+            if (info && info.outdatedReadVariables.has(variable)) {
                 return true;
             }
         }
@@ -214,14 +218,13 @@ module.exports = {
                 if (!reference) {
                     return;
                 }
-                const name = reference.identifier.name;
                 const variable = reference.resolved;
                 const writeExpr = getWriteExpr(reference);
                 const isMemberAccess = reference.identifier.parent.type === "MemberExpression";
 
                 // Add a fresh read variable.
                 if (reference.isRead() && !(writeExpr && writeExpr.parent.operator === "=")) {
-                    segmentInfo.markAsRead(codePath.currentSegments, name);
+                    segmentInfo.markAsRead(codePath.currentSegments, variable);
                 }
 
                 /*
@@ -245,7 +248,7 @@ module.exports = {
 
             /*
              * Verify assignments.
-             * If the reference exists in `outdatedReadVariableNames` list, report it.
+             * If the reference exists in `outdatedReadVariables` list, report it.
              */
             ":expression:exit"(node) {
                 const { codePath, referenceMap } = stack;
@@ -267,9 +270,9 @@ module.exports = {
                     assignmentReferences.delete(node);
 
                     for (const reference of references) {
-                        const name = reference.identifier.name;
+                        const variable = reference.resolved;
 
-                        if (segmentInfo.isOutdated(codePath.currentSegments, name)) {
+                        if (segmentInfo.isOutdated(codePath.currentSegments, variable)) {
                             context.report({
                                 node: node.parent,
                                 messageId: "nonAtomicUpdate",
diff --git a/package.json b/package.json
index 4f87ef640..72070e2a0 100644
--- a/package.json
+++ b/package.json
@@ -84,7 +84,6 @@
   "devDependencies": {
     "@babel/core": "^7.4.3",
     "@babel/preset-env": "^7.4.3",
-    "acorn": "^7.2.0",
     "babel-loader": "^8.0.5",
     "chai": "^4.0.1",
     "cheerio": "^0.22.0",


Test patch:
diff --git a/tests/lib/init/config-initializer.js b/tests/lib/init/config-initializer.js
index 10a67fe0a..81e4e52fa 100644
--- a/tests/lib/init/config-initializer.js
+++ b/tests/lib/init/config-initializer.js
@@ -27,6 +27,8 @@ const proxyquire = require("proxyquire").noPreserveCache();
 //------------------------------------------------------------------------------
 
 let answers = {};
+let pkgJSONContents = {};
+let pkgJSONPath = "";
 
 describe("configInitializer", () => {
 
@@ -455,4 +457,121 @@ describe("configInitializer", () => {
             });
         });
     });
+
+    describe("writeFile()", () => {
+
+        beforeEach(() => {
+            answers = {
+                purpose: "style",
+                source: "prompt",
+                extendDefault: true,
+                indent: 2,
+                quotes: "single",
+                linebreak: "unix",
+                semi: true,
+                moduleType: "esm",
+                es6Globals: true,
+                env: ["browser"],
+                format: "JSON"
+            };
+
+            pkgJSONContents = {
+                name: "config-initializer",
+                version: "1.0.0"
+            };
+
+            process.chdir(fixtureDir);
+
+            pkgJSONPath = path.resolve(fixtureDir, "package.json");
+        });
+
+        afterEach(() => {
+            process.chdir(originalDir);
+        });
+
+        it("should create .eslintrc.json", () => {
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.json");
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        it("should create .eslintrc.js", () => {
+            answers.format = "JavaScript";
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.js");
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        it("should create .eslintrc.yml", () => {
+            answers.format = "YAML";
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.yml");
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        // For https://github.com/eslint/eslint/issues/14137
+        it("should create .eslintrc.cjs", () => {
+            answers.format = "JavaScript";
+
+            // create package.json with "type": "module"
+            pkgJSONContents.type = "module";
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.cjs");
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+
+        it("should create .eslintrc.json even with type: 'module'", () => {
+            answers.format = "JSON";
+
+            // create package.json with "type": "module"
+            pkgJSONContents.type = "module";
+
+            fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents));
+
+            const config = init.processAnswers(answers);
+            const filePath = path.resolve(fixtureDir, ".eslintrc.json");
+
+            init.writeFile(config, answers.format);
+
+            assert.isTrue(fs.existsSync(filePath));
+
+            fs.unlinkSync(filePath);
+            fs.unlinkSync(pkgJSONPath);
+        });
+    });
 });
diff --git a/tests/lib/rules/no-redeclare.js b/tests/lib/rules/no-redeclare.js
index f6293b7e1..f89d6853f 100644
--- a/tests/lib/rules/no-redeclare.js
+++ b/tests/lib/rules/no-redeclare.js
@@ -9,7 +9,6 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const path = require("path");
 const rule = require("../../../lib/rules/no-redeclare");
 const { RuleTester } = require("../../../lib/rule-tester");
 
@@ -17,7 +16,6 @@ const { RuleTester } = require("../../../lib/rule-tester");
 // Tests
 //------------------------------------------------------------------------------
 
-const looseParserPath = path.resolve(__dirname, "../../tools/loose-parser.js");
 const ruleTester = new RuleTester();
 
 ruleTester.run("no-redeclare", rule, {
@@ -205,112 +203,6 @@ ruleTester.run("no-redeclare", rule, {
             ]
         },
 
-        // let/const
-        {
-            code: "let a; let a;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "let a; let a;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015, sourceType: "module" },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "let a; let a;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015, ecmaFeatures: { globalReturn: true } },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "let a; const a = 0;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "const a = 0; const a = 0;",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "if (test) { let a; let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "switch (test) { case 0: let a; let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "for (let a, a;;);",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "for (let [a, a] in xs);",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "for (let [a, a] of xs);",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "function f() { let a; let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "function f(a) { let a; }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-        {
-            code: "function f() { if (test) { let a; let a; } }",
-            parser: looseParserPath,
-            parserOptions: { ecmaVersion: 2015 },
-            errors: [
-                { message: "'a' is already defined.", type: "Identifier" }
-            ]
-        },
-
         // Comments and built-ins.
         {
             code: "/*globals Array */",
diff --git a/tests/lib/rules/require-atomic-updates.js b/tests/lib/rules/require-atomic-updates.js
index cd51e38ff..bd3738ac9 100644
--- a/tests/lib/rules/require-atomic-updates.js
+++ b/tests/lib/rules/require-atomic-updates.js
@@ -53,6 +53,7 @@ ruleTester.run("require-atomic-updates", rule, {
         "let foo; async function x() { foo = condition ? foo : await bar; }",
         "async function x() { let foo; bar(() => { let foo; blah(foo); }); foo += await result; }",
         "let foo; async function x() { foo = foo + 1; await bar; }",
+        "async function x() { foo += await bar; }",
 
 
         /*
@@ -162,6 +163,52 @@ ruleTester.run("require-atomic-updates", rule, {
                 count -= 1
                 return
             }
+        `,
+
+        // https://github.com/eslint/eslint/issues/14208
+        `
+            async function foo(e) {
+            }
+
+            async function run() {
+              const input = [];
+              const props = [];
+
+              for(const entry of input) {
+                const prop = props.find(a => a.id === entry.id) || null;
+                await foo(entry);
+              }
+
+              for(const entry of input) {
+                const prop = props.find(a => a.id === entry.id) || null;
+              }
+
+              for(const entry2 of input) {
+                const prop = props.find(a => a.id === entry2.id) || null;
+              }
+            }
+        `,
+
+        `
+            async function run() {
+              {
+                let entry;
+                await entry;
+              }
+              {
+                let entry;
+                () => entry;
+
+                entry = 1;
+              }
+            }
+        `,
+
+        `
+            async function run() {
+                await a;
+                b = 1;
+            }
         `
     ],
 
@@ -251,7 +298,7 @@ ruleTester.run("require-atomic-updates", rule, {
             errors: [COMPUTED_PROPERTY_ERROR, STATIC_PROPERTY_ERROR]
         },
         {
-            code: "async function x() { foo += await bar; }",
+            code: "let foo = ''; async function x() { foo += await bar; }",
             errors: [VARIABLE_ERROR]
         },
         {
diff --git a/tests/tools/loose-parser.js b/tests/tools/loose-parser.js
deleted file mode 100644
index 98e97cc36..000000000
--- a/tests/tools/loose-parser.js
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * @fileoverview Define a custom parser to ignore recoverable syntax errors.
- * @author Toru Nagashima <https://github.com/mysticatea>
- *
- * no-redeclare rule uses this parser to check redeclarations.
- */
-"use strict";
-
-const acorn = require("acorn");
-const espree = require("espree/lib/espree");
-
-/**
- * Define the parser which ignores recoverable errors.
- * @returns {(parser:acorn.Parser) => acorn.Parser} The function that defines loose parser.
- */
-function loose() {
-    return Parser => class LooseParser extends Parser {
-        raiseRecoverable() { // eslint-disable-line class-methods-use-this
-            // ignore
-        }
-    };
-}
-
-const LooseEspree = acorn.Parser.extend(espree(), loose());
-
-module.exports = {
-    parse(code, options) {
-        return new LooseEspree(options, code).parse();
-    }
-};