diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index fd77a7dceba..63b5b6e7f32 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -185,36 +185,37 @@ In these cases, we create what we call an extension rule; a rule within our plug
**Key**: :heavy_check_mark: = recommended, :wrench: = fixable, :thought_balloon: = requires type information
-| Name | Description | :heavy_check_mark: | :wrench: | :thought_balloon: |
-| ----------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------ | -------- | ----------------- |
-| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
-| [`@typescript-eslint/comma-spacing`](./docs/rules/comma-spacing.md) | Enforces consistent spacing before and after commas | | :wrench: | |
-| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | |
-| [`@typescript-eslint/dot-notation`](./docs/rules/dot-notation.md) | enforce dot notation whenever possible | | :wrench: | :thought_balloon: |
-| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | |
-| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | |
-| [`@typescript-eslint/init-declarations`](./docs/rules/init-declarations.md) | require or disallow initialization in variable declarations | | | |
-| [`@typescript-eslint/keyword-spacing`](./docs/rules/keyword-spacing.md) | Enforce consistent spacing before and after keywords | | :wrench: | |
-| [`@typescript-eslint/lines-between-class-members`](./docs/rules/lines-between-class-members.md) | Require or disallow an empty line between class members | | :wrench: | |
-| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
-| [`@typescript-eslint/no-dupe-class-members`](./docs/rules/no-dupe-class-members.md) | Disallow duplicate class members | | | |
-| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | |
-| [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | |
-| [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | :heavy_check_mark: | :wrench: | |
-| [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | disallow `this` keywords outside of classes or class-like objects | | | |
-| [`@typescript-eslint/no-loss-of-precision`](./docs/rules/no-loss-of-precision.md) | Disallow literal numbers that lose precision | | | |
-| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | |
-| [`@typescript-eslint/no-redeclare`](./docs/rules/no-redeclare.md) | Disallow variable redeclaration | | | |
-| [`@typescript-eslint/no-shadow`](./docs/rules/no-shadow.md) | Disallow variable declarations from shadowing variables declared in the outer scope | | | |
-| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
-| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
-| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | |
-| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | |
-| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
-| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: |
-| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: |
-| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
-| [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | |
+| Name | Description | :heavy_check_mark: | :wrench: | :thought_balloon: |
+| ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------ | -------- | ----------------- |
+| [`@typescript-eslint/brace-style`](./docs/rules/brace-style.md) | Enforce consistent brace style for blocks | | :wrench: | |
+| [`@typescript-eslint/comma-spacing`](./docs/rules/comma-spacing.md) | Enforces consistent spacing before and after commas | | :wrench: | |
+| [`@typescript-eslint/default-param-last`](./docs/rules/default-param-last.md) | Enforce default parameters to be last | | | |
+| [`@typescript-eslint/dot-notation`](./docs/rules/dot-notation.md) | enforce dot notation whenever possible | | :wrench: | :thought_balloon: |
+| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | |
+| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | | :wrench: | |
+| [`@typescript-eslint/init-declarations`](./docs/rules/init-declarations.md) | require or disallow initialization in variable declarations | | | |
+| [`@typescript-eslint/keyword-spacing`](./docs/rules/keyword-spacing.md) | Enforce consistent spacing before and after keywords | | :wrench: | |
+| [`@typescript-eslint/lines-between-class-members`](./docs/rules/lines-between-class-members.md) | Require or disallow an empty line between class members | | :wrench: | |
+| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | |
+| [`@typescript-eslint/no-dupe-class-members`](./docs/rules/no-dupe-class-members.md) | Disallow duplicate class members | | | |
+| [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | :heavy_check_mark: | | |
+| [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | |
+| [`@typescript-eslint/no-extra-semi`](./docs/rules/no-extra-semi.md) | Disallow unnecessary semicolons | :heavy_check_mark: | :wrench: | |
+| [`@typescript-eslint/no-invalid-this`](./docs/rules/no-invalid-this.md) | disallow `this` keywords outside of classes or class-like objects | | | |
+| [`@typescript-eslint/no-loop-func`](./docs/rules/no-loop-func.md) | Disallow function declarations that contain unsafe references inside loop statements | | | |
+| [`@typescript-eslint/no-loss-of-precision`](./docs/rules/no-loss-of-precision.md) | Disallow literal numbers that lose precision | | | |
+| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallow magic numbers | | | |
+| [`@typescript-eslint/no-redeclare`](./docs/rules/no-redeclare.md) | Disallow variable redeclaration | | | |
+| [`@typescript-eslint/no-shadow`](./docs/rules/no-shadow.md) | Disallow variable declarations from shadowing variables declared in the outer scope | | | |
+| [`@typescript-eslint/no-unused-expressions`](./docs/rules/no-unused-expressions.md) | Disallow unused expressions | | | |
+| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | |
+| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | | | |
+| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | |
+| [`@typescript-eslint/quotes`](./docs/rules/quotes.md) | Enforce the consistent use of either backticks, double, or single quotes | | :wrench: | |
+| [`@typescript-eslint/require-await`](./docs/rules/require-await.md) | Disallow async functions which have no `await` expression | :heavy_check_mark: | | :thought_balloon: |
+| [`@typescript-eslint/return-await`](./docs/rules/return-await.md) | Enforces consistent returning of awaited values | | :wrench: | :thought_balloon: |
+| [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | |
+| [`@typescript-eslint/space-before-function-paren`](./docs/rules/space-before-function-paren.md) | Enforces consistent spacing before function parenthesis | | :wrench: | |
diff --git a/packages/eslint-plugin/docs/rules/no-loop-func.md b/packages/eslint-plugin/docs/rules/no-loop-func.md
new file mode 100644
index 00000000000..d48d38c20f6
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-loop-func.md
@@ -0,0 +1,22 @@
+# Disallow function declarations that contain unsafe references inside loop statements (`no-loop-func`)
+
+## Rule Details
+
+This rule extends the base [`eslint/no-loop-func`](https://eslint.org/docs/rules/no-loop-func) rule.
+It adds support for TypeScript types.
+
+## How to use
+
+```cjson
+{
+ // note you must disable the base rule as it can report incorrect errors
+ "no-loop-func": "off",
+ "@typescript-eslint/no-loop-func": ["error"]
+}
+```
+
+## Options
+
+See [`eslint/no-loop-func` options](https://eslint.org/docs/rules/no-loop-func#options).
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-loop-func.md)
diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts
index c3edea830e8..7e457d7aa1f 100644
--- a/packages/eslint-plugin/src/configs/all.ts
+++ b/packages/eslint-plugin/src/configs/all.ts
@@ -65,6 +65,8 @@ export = {
'no-invalid-this': 'off',
'@typescript-eslint/no-invalid-this': 'error',
'@typescript-eslint/no-invalid-void-type': 'error',
+ 'no-loop-func': 'off',
+ '@typescript-eslint/no-loop-func': 'error',
'no-loss-of-precision': 'off',
'@typescript-eslint/no-loss-of-precision': 'error',
'no-magic-numbers': 'off',
diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts
index f909be42d4e..fa8dba93ed1 100644
--- a/packages/eslint-plugin/src/rules/index.ts
+++ b/packages/eslint-plugin/src/rules/index.ts
@@ -45,6 +45,7 @@ import noInferrableTypes from './no-inferrable-types';
import noInvalidThis from './no-invalid-this';
import noInvalidVoidType from './no-invalid-void-type';
import noLossOfPrecision from './no-loss-of-precision';
+import noLoopFunc from './no-loop-func';
import noMagicNumbers from './no-magic-numbers';
import noMisusedNew from './no-misused-new';
import noMisusedPromises from './no-misused-promises';
@@ -114,7 +115,6 @@ export default {
'brace-style': braceStyle,
'class-literal-property-style': classLiteralPropertyStyle,
'comma-spacing': commaSpacing,
- 'no-confusing-non-null-assertion': confusingNonNullAssertionLikeNotEqual,
'consistent-type-assertions': consistentTypeAssertions,
'consistent-type-definitions': consistentTypeDefinitions,
'consistent-type-imports': consistentTypeImports,
@@ -124,31 +124,34 @@ export default {
'explicit-member-accessibility': explicitMemberAccessibility,
'explicit-module-boundary-types': explicitModuleBoundaryTypes,
'func-call-spacing': funcCallSpacing,
- indent: indent,
'init-declarations': initDeclarations,
'keyword-spacing': keywordSpacing,
+ 'lines-between-class-members': linesBetweenClassMembers,
'member-delimiter-style': memberDelimiterStyle,
'member-ordering': memberOrdering,
'method-signature-style': methodSignatureStyle,
'naming-convention': namingConvention,
'no-array-constructor': noArrayConstructor,
'no-base-to-string': noBaseToString,
+ 'no-confusing-non-null-assertion': confusingNonNullAssertionLikeNotEqual,
'no-dupe-class-members': noDupeClassMembers,
'no-dynamic-delete': noDynamicDelete,
'no-empty-function': noEmptyFunction,
'no-empty-interface': noEmptyInterface,
'no-explicit-any': noExplicitAny,
- 'no-implicit-any-catch': noImplicitAnyCatch,
'no-extra-non-null-assertion': noExtraNonNullAssertion,
'no-extra-parens': noExtraParens,
'no-extra-semi': noExtraSemi,
'no-extraneous-class': noExtraneousClass,
'no-floating-promises': noFloatingPromises,
'no-for-in-array': noForInArray,
+ 'no-implicit-any-catch': noImplicitAnyCatch,
'no-implied-eval': noImpliedEval,
'no-inferrable-types': noInferrableTypes,
'no-invalid-this': noInvalidThis,
'no-invalid-void-type': noInvalidVoidType,
+ 'no-loop-func': noLoopFunc,
+ 'no-loss-of-precision': noLossOfPrecision,
'no-magic-numbers': noMagicNumbers,
'no-misused-new': noMisusedNew,
'no-misused-promises': noMisusedPromises,
@@ -193,21 +196,20 @@ export default {
'prefer-string-starts-ends-with': preferStringStartsEndsWith,
'prefer-ts-expect-error': preferTsExpectError,
'promise-function-async': promiseFunctionAsync,
- quotes: quotes,
'require-array-sort-compare': requireArraySortCompare,
'require-await': requireAwait,
'restrict-plus-operands': restrictPlusOperands,
'restrict-template-expressions': restrictTemplateExpressions,
'return-await': returnAwait,
- semi: semi,
'space-before-function-paren': spaceBeforeFunctionParen,
'strict-boolean-expressions': strictBooleanExpressions,
'switch-exhaustiveness-check': switchExhaustivenessCheck,
'triple-slash-reference': tripleSlashReference,
'type-annotation-spacing': typeAnnotationSpacing,
- typedef: typedef,
'unbound-method': unboundMethod,
'unified-signatures': unifiedSignatures,
- 'lines-between-class-members': linesBetweenClassMembers,
- 'no-loss-of-precision': noLossOfPrecision,
+ indent: indent,
+ quotes: quotes,
+ semi: semi,
+ typedef: typedef,
};
diff --git a/packages/eslint-plugin/src/rules/no-loop-func.ts b/packages/eslint-plugin/src/rules/no-loop-func.ts
new file mode 100644
index 00000000000..44d0178e867
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-loop-func.ts
@@ -0,0 +1,220 @@
+import {
+ AST_NODE_TYPES,
+ TSESLint,
+ TSESTree,
+} from '@typescript-eslint/experimental-utils';
+import baseRule from 'eslint/lib/rules/no-loop-func';
+import * as util from '../util';
+
+type Options = util.InferOptionsTypeFromRule;
+type MessageIds = util.InferMessageIdsTypeFromRule;
+
+export default util.createRule({
+ name: 'no-loop-func',
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'Disallow function declarations that contain unsafe references inside loop statements',
+ category: 'Best Practices',
+ recommended: false,
+ extendsBaseRule: true,
+ },
+ schema: [],
+ messages: baseRule?.meta.messages ?? {
+ unsafeRefs:
+ 'Function declared in a loop contains unsafe references to variable(s) {{ varNames }}.',
+ },
+ },
+ defaultOptions: [],
+ create(context) {
+ /**
+ * Reports functions which match the following condition:
+ * - has a loop node in ancestors.
+ * - has any references which refers to an unsafe variable.
+ *
+ * @param node The AST node to check.
+ * @returns Whether or not the node is within a loop.
+ */
+ function checkForLoops(
+ node:
+ | TSESTree.ArrowFunctionExpression
+ | TSESTree.FunctionExpression
+ | TSESTree.FunctionDeclaration,
+ ): void {
+ const loopNode = getContainingLoopNode(node);
+
+ if (!loopNode) {
+ return;
+ }
+
+ const references = context.getScope().through;
+ const unsafeRefs = references
+ .filter(r => !isSafe(loopNode, r))
+ .map(r => r.identifier.name);
+
+ if (unsafeRefs.length > 0) {
+ context.report({
+ node,
+ messageId: 'unsafeRefs',
+ data: { varNames: `'${unsafeRefs.join("', '")}'` },
+ });
+ }
+ }
+
+ return {
+ ArrowFunctionExpression: checkForLoops,
+ FunctionExpression: checkForLoops,
+ FunctionDeclaration: checkForLoops,
+ };
+ },
+});
+
+/**
+ * Gets the containing loop node of a specified node.
+ *
+ * We don't need to check nested functions, so this ignores those.
+ * `Scope.through` contains references of nested functions.
+ *
+ * @param node An AST node to get.
+ * @returns The containing loop node of the specified node, or `null`.
+ */
+function getContainingLoopNode(node: TSESTree.Node): TSESTree.Node | null {
+ for (
+ let currentNode = node;
+ currentNode.parent;
+ currentNode = currentNode.parent
+ ) {
+ const parent = currentNode.parent;
+
+ switch (parent.type) {
+ case AST_NODE_TYPES.WhileStatement:
+ case AST_NODE_TYPES.DoWhileStatement:
+ return parent;
+
+ case AST_NODE_TYPES.ForStatement:
+ // `init` is outside of the loop.
+ if (parent.init !== currentNode) {
+ return parent;
+ }
+ break;
+
+ case AST_NODE_TYPES.ForInStatement:
+ case AST_NODE_TYPES.ForOfStatement:
+ // `right` is outside of the loop.
+ if (parent.right !== currentNode) {
+ return parent;
+ }
+ break;
+
+ case AST_NODE_TYPES.ArrowFunctionExpression:
+ case AST_NODE_TYPES.FunctionExpression:
+ case AST_NODE_TYPES.FunctionDeclaration:
+ // We don't need to check nested functions.
+ return null;
+
+ default:
+ break;
+ }
+ }
+
+ return null;
+}
+
+/**
+ * Gets the containing loop node of a given node.
+ * If the loop was nested, this returns the most outer loop.
+ * @param node A node to get. This is a loop node.
+ * @param excludedNode A node that the result node should not include.
+ * @returns The most outer loop node.
+ */
+function getTopLoopNode(
+ node: TSESTree.Node,
+ excludedNode: TSESTree.Node | null | undefined,
+): TSESTree.Node {
+ const border = excludedNode ? excludedNode.range[1] : 0;
+ let retv = node;
+ let containingLoopNode: TSESTree.Node | null = node;
+
+ while (containingLoopNode && containingLoopNode.range[0] >= border) {
+ retv = containingLoopNode;
+ containingLoopNode = getContainingLoopNode(containingLoopNode);
+ }
+
+ return retv;
+}
+
+/**
+ * Checks whether a given reference which refers to an upper scope's variable is
+ * safe or not.
+ * @param loopNode A containing loop node.
+ * @param reference A reference to check.
+ * @returns `true` if the reference is safe or not.
+ */
+function isSafe(
+ loopNode: TSESTree.Node,
+ reference: TSESLint.Scope.Reference,
+): boolean {
+ const variable = reference.resolved;
+ const definition = variable?.defs[0];
+ const declaration = definition?.parent;
+ const kind =
+ declaration?.type === AST_NODE_TYPES.VariableDeclaration
+ ? declaration.kind
+ : '';
+
+ // type references are all safe
+ // this only really matters for global types that haven't been configured
+ if (reference.isTypeReference) {
+ return true;
+ }
+
+ // Variables which are declared by `const` is safe.
+ if (kind === 'const') {
+ return true;
+ }
+
+ /*
+ * Variables which are declared by `let` in the loop is safe.
+ * It's a different instance from the next loop step's.
+ */
+ if (
+ kind === 'let' &&
+ declaration &&
+ declaration.range[0] > loopNode.range[0] &&
+ declaration.range[1] < loopNode.range[1]
+ ) {
+ return true;
+ }
+
+ /*
+ * WriteReferences which exist after this border are unsafe because those
+ * can modify the variable.
+ */
+ const border = getTopLoopNode(loopNode, kind === 'let' ? declaration : null)
+ .range[0];
+
+ /**
+ * Checks whether a given reference is safe or not.
+ * The reference is every reference of the upper scope's variable we are
+ * looking now.
+ *
+ * It's safe if the reference matches one of the following condition.
+ * - is readonly.
+ * - doesn't exist inside a local function and after the border.
+ *
+ * @param upperRef A reference to check.
+ * @returns `true` if the reference is safe.
+ */
+ function isSafeReference(upperRef: TSESLint.Scope.Reference): boolean {
+ const id = upperRef.identifier;
+
+ return (
+ !upperRef.isWrite() ||
+ (variable?.scope?.variableScope === upperRef.from.variableScope &&
+ id.range[0] < border)
+ );
+ }
+
+ return variable?.references.every(isSafeReference) ?? false;
+}
diff --git a/packages/eslint-plugin/tests/rules/no-loop-func.test.ts b/packages/eslint-plugin/tests/rules/no-loop-func.test.ts
new file mode 100644
index 00000000000..be22ae267c0
--- /dev/null
+++ b/packages/eslint-plugin/tests/rules/no-loop-func.test.ts
@@ -0,0 +1,738 @@
+import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
+import rule from '../../src/rules/no-loop-func';
+import { RuleTester } from '../RuleTester';
+
+const ruleTester = new RuleTester({
+ parser: '@typescript-eslint/parser',
+});
+
+ruleTester.run('no-loop-func', rule, {
+ valid: [
+ `
+let someArray: MyType[] = [];
+for (let i = 0; i < 10; i += 1) {
+ someArray = someArray.filter((item: MyType) => !!item);
+}
+ `,
+ {
+ code: `
+let someArray: MyType[] = [];
+for (let i = 0; i < 10; i += 1) {
+ someArray = someArray.filter((item: MyType) => !!item);
+}
+ `,
+ globals: {
+ MyType: 'readonly',
+ },
+ },
+ {
+ code: `
+let someArray: MyType[] = [];
+for (let i = 0; i < 10; i += 1) {
+ someArray = someArray.filter((item: MyType) => !!item);
+}
+ `,
+ globals: {
+ MyType: 'writable',
+ },
+ },
+ `
+type MyType = 1;
+let someArray: MyType[] = [];
+for (let i = 0; i < 10; i += 1) {
+ someArray = someArray.filter((item: MyType) => !!item);
+}
+ `,
+ ],
+ invalid: [],
+});
+
+// Forked from https://github.com/eslint/eslint/blob/bf2e367bf4f6fde9930af9de8b8d8bc3d8b5782f/tests/lib/rules/no-loop-func.js
+ruleTester.run('no-loop-func ESLint tests', rule, {
+ valid: [
+ "string = 'function a() {}';",
+ `
+for (var i = 0; i < l; i++) {}
+var a = function () {
+ i;
+};
+ `,
+ `
+for (
+ var i = 0,
+ a = function () {
+ i;
+ };
+ i < l;
+ i++
+) {}
+ `,
+ `
+for (var x in xs.filter(function (x) {
+ return x != upper;
+})) {
+}
+ `,
+ {
+ code: `
+for (var x of xs.filter(function (x) {
+ return x != upper;
+})) {
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+
+ // no refers to variables that declared on upper scope.
+ `
+for (var i = 0; i < l; i++) {
+ (function () {});
+}
+ `,
+ `
+for (var i in {}) {
+ (function () {});
+}
+ `,
+ {
+ code: `
+for (var i of {}) {
+ (function () {});
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+
+ // functions which are using unmodified variables are OK.
+ {
+ code: `
+for (let i = 0; i < l; i++) {
+ (function () {
+ i;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+for (let i in {}) {
+ i = 7;
+ (function () {
+ i;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+for (const i of {}) {
+ (function () {
+ i;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+for (let i = 0; i < 10; ++i) {
+ for (let x in xs.filter(x => x != i)) {
+ }
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+let a = 0;
+for (let i = 0; i < l; i++) {
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+let a = 0;
+for (let i in {}) {
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+let a = 0;
+for (let i of {}) {
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+let a = 0;
+for (let i = 0; i < l; i++) {
+ (function () {
+ (function () {
+ a;
+ });
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+let a = 0;
+for (let i in {}) {
+ function foo() {
+ (function () {
+ a;
+ });
+ }
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+let a = 0;
+for (let i of {}) {
+ () => {
+ (function () {
+ a;
+ });
+ };
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+var a = 0;
+for (let i = 0; i < l; i++) {
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+var a = 0;
+for (let i in {}) {
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: `
+var a = 0;
+for (let i of {}) {
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: [
+ 'let result = {};',
+ 'for (const score in scores) {',
+ ' const letters = scores[score];',
+ " letters.split('').forEach(letter => {",
+ ' result[letter] = score;',
+ ' });',
+ '}',
+ 'result.__default = 6;',
+ ].join('\n'),
+ parserOptions: { ecmaVersion: 6 },
+ },
+ {
+ code: ['while (true) {', ' (function() { a; });', '}', 'let a;'].join(
+ '\n',
+ ),
+ parserOptions: { ecmaVersion: 6 },
+ },
+ ],
+ invalid: [
+ {
+ code: `
+for (var i = 0; i < l; i++) {
+ (function () {
+ i;
+ });
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i = 0; i < l; i++) {
+ for (var j = 0; j < m; j++) {
+ (function () {
+ i + j;
+ });
+ }
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i', 'j'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i in {}) {
+ (function () {
+ i;
+ });
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i of {}) {
+ (function () {
+ i;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i = 0; i < l; i++) {
+ () => {
+ i;
+ };
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.ArrowFunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i = 0; i < l; i++) {
+ var a = function () {
+ i;
+ };
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i = 0; i < l; i++) {
+ function a() {
+ i;
+ }
+ a();
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionDeclaration,
+ },
+ ],
+ },
+ {
+ code: `
+for (
+ var i = 0;
+ (function () {
+ i;
+ })(),
+ i < l;
+ i++
+) {}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (
+ var i = 0;
+ i < l;
+ (function () {
+ i;
+ })(),
+ i++
+) {}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+while (i) {
+ (function () {
+ i;
+ });
+}
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+do {
+ (function () {
+ i;
+ });
+} while (i);
+ `,
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+
+ // Warns functions which are using modified variables.
+ {
+ code: `
+let a;
+for (let i = 0; i < l; i++) {
+ a = 1;
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+for (let i in {}) {
+ (function () {
+ a;
+ });
+ a = 1;
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+for (let i of {}) {
+ (function () {
+ a;
+ });
+}
+a = 1;
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+for (let i = 0; i < l; i++) {
+ (function () {
+ (function () {
+ a;
+ });
+ });
+ a = 1;
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+for (let i in {}) {
+ a = 1;
+ function foo() {
+ (function () {
+ a;
+ });
+ }
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionDeclaration,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+for (let i of {}) {
+ () => {
+ (function () {
+ a;
+ });
+ };
+}
+a = 1;
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.ArrowFunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var i = 0; i < 10; ++i) {
+ for (let x in xs.filter(x => x != i)) {
+ }
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'i'" },
+ type: AST_NODE_TYPES.ArrowFunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (let x of xs) {
+ let a;
+ for (let y of ys) {
+ a = 1;
+ (function () {
+ a;
+ });
+ }
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var x of xs) {
+ for (let y of ys) {
+ (function () {
+ x;
+ });
+ }
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'x'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+for (var x of xs) {
+ (function () {
+ x;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'x'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+var a;
+for (let x of xs) {
+ a = 1;
+ (function () {
+ a;
+ });
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+var a;
+for (let x of xs) {
+ (function () {
+ a;
+ });
+ a = 1;
+}
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+function foo() {
+ a = 10;
+}
+for (let x of xs) {
+ (function () {
+ a;
+ });
+}
+foo();
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ {
+ code: `
+let a;
+function foo() {
+ a = 10;
+ for (let x of xs) {
+ (function () {
+ a;
+ });
+ }
+}
+foo();
+ `,
+ parserOptions: { ecmaVersion: 6 },
+ errors: [
+ {
+ messageId: 'unsafeRefs',
+ data: { varNames: "'a'" },
+ type: AST_NODE_TYPES.FunctionExpression,
+ },
+ ],
+ },
+ ],
+});
diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts
index fa9fc6170a6..9117f06c6c1 100644
--- a/packages/eslint-plugin/typings/eslint-rules.d.ts
+++ b/packages/eslint-plugin/typings/eslint-rules.d.ts
@@ -286,6 +286,21 @@ declare module 'eslint/lib/rules/no-implicit-globals' {
export = rule;
}
+declare module 'eslint/lib/rules/no-loop-func' {
+ import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';
+
+ const rule: TSESLint.RuleModule<
+ 'unsafeRefs',
+ [],
+ {
+ ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void;
+ FunctionExpression(node: TSESTree.FunctionExpression): void;
+ FunctionDeclaration(node: TSESTree.FunctionDeclaration): void;
+ }
+ >;
+ export = rule;
+}
+
declare module 'eslint/lib/rules/no-magic-numbers' {
import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils';