Skip to content

Commit 71769f3

Browse files
feat(no-let): add option to allow lets inside of for loop initializers (#306)
1 parent d3348b9 commit 71769f3

File tree

5 files changed

+76
-4
lines changed

5 files changed

+76
-4
lines changed

docs/rules/no-let.md

+34
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,44 @@ The default options:
6262
6363
```ts
6464
const defaults = {
65+
allowInForLoopInit: false,
6566
allowLocalMutation: false
6667
}
6768
```
6869

70+
### `allowInForLoopInit`
71+
72+
If set, `let`s inside of for a loop initializer are allowed. This does not include for...of or for...in loops.
73+
74+
Examples of **correct** code for this rule:
75+
76+
```js
77+
/* eslint functional/no-let: ["error", { "allowInForLoopInit": true } ] */
78+
79+
for (let i = 0; i < array.length; i++) {
80+
}
81+
```
82+
83+
Examples of **incorrect** code for this rule:
84+
85+
<!-- eslint-skip -->
86+
87+
```js
88+
/* eslint functional/no-let: "error" */
89+
90+
for (let element of array) {
91+
}
92+
```
93+
94+
<!-- eslint-skip -->
95+
96+
```js
97+
/* eslint functional/no-let: "error" */
98+
99+
for (let [index, element] of array.entries()) {
100+
}
101+
```
102+
69103
### `allowLocalMutation`
70104

71105
See the [allowLocalMutation](./options/allow-local-mutation.md) docs.

src/rules/no-let.ts

+19-4
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,33 @@ import {
1414
} from "~/common/ignore-options";
1515
import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
1616
import { createRule } from "~/util/rule";
17+
import { inForLoopInitializer } from "~/util/tree";
1718

1819
// The name of this rule.
1920
export const name = "no-let" as const;
2021

2122
// The options this rule can take.
22-
type Options = AllowLocalMutationOption & IgnorePatternOption;
23+
type Options = AllowLocalMutationOption &
24+
IgnorePatternOption & {
25+
readonly allowInForLoopInit: boolean;
26+
};
2327

2428
// The schema for the rule options.
2529
const schema: JSONSchema4 = [
26-
deepmerge(allowLocalMutationOptionSchema, ignorePatternOptionSchema),
30+
deepmerge(allowLocalMutationOptionSchema, ignorePatternOptionSchema, {
31+
type: "object",
32+
properties: {
33+
allowInForLoopInit: {
34+
type: "boolean",
35+
},
36+
},
37+
additionalProperties: false,
38+
}),
2739
];
2840

2941
// The default options for the rule.
3042
const defaultOptions: Options = {
43+
allowInForLoopInit: false,
3144
allowLocalMutation: false,
3245
};
3346

@@ -57,8 +70,10 @@ function checkVariableDeclaration(
5770
options: Options
5871
): RuleResult<keyof typeof errorMessages, Options> {
5972
if (
73+
node.kind !== "let" ||
6074
shouldIgnoreLocalMutation(node, context, options) ||
61-
shouldIgnorePattern(node, context, options)
75+
shouldIgnorePattern(node, context, options) ||
76+
(options.allowInForLoopInit && inForLoopInitializer(node))
6277
) {
6378
return {
6479
context,
@@ -68,7 +83,7 @@ function checkVariableDeclaration(
6883

6984
return {
7085
context,
71-
descriptors: node.kind === "let" ? [{ node, messageId: "generic" }] : [],
86+
descriptors: [{ node, messageId: "generic" }],
7287
};
7388
}
7489

src/util/tree.ts

+13
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
isCallExpression,
66
isClassLike,
77
isDefined,
8+
isForStatement,
89
isFunctionExpressionLike,
910
isFunctionLike,
1011
isIdentifier,
@@ -53,6 +54,18 @@ export function inClass(node: TSESTree.Node): boolean {
5354
return getAncestorOfType(isClassLike, node) !== null;
5455
}
5556

57+
/**
58+
* Test if the given node is in a for loop initializer.
59+
*/
60+
export function inForLoopInitializer(node: TSESTree.Node): boolean {
61+
return (
62+
getAncestorOfType(
63+
(n, c): n is TSESTree.ForStatement => isForStatement(n) && n.init === c,
64+
node
65+
) !== null
66+
);
67+
}
68+
5669
/**
5770
* Test if the given node is shallowly inside a `Readonly<{...}>`.
5871
*/

src/util/typeguard.ts

+6
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,12 @@ export function isExpressionStatement(
118118
return node.type === AST_NODE_TYPES.ExpressionStatement;
119119
}
120120

121+
export function isForStatement(
122+
node: TSESTree.Node
123+
): node is TSESTree.ForStatement {
124+
return node.type === AST_NODE_TYPES.ForStatement;
125+
}
126+
121127
export function isFunctionDeclaration(
122128
node: TSESTree.Node
123129
): node is TSESTree.FunctionDeclaration {

tests/rules/no-let/es6/valid.ts

+4
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ const tests: ReadonlyArray<ValidTestCase> = [
142142
`,
143143
optionsSet: [[{ ignorePattern: "Mutable$" }]],
144144
},
145+
{
146+
code: `for (let x = 0; x < 1; x++);`,
147+
optionsSet: [[{ allowInForLoopInit: true }]],
148+
},
145149
];
146150

147151
export default tests;

0 commit comments

Comments
 (0)