Skip to content

Commit

Permalink
feat(no-let): add option to allow lets inside of for loop initializers
Browse files Browse the repository at this point in the history
fix #270
  • Loading branch information
RebeccaStevens committed Jan 8, 2022
1 parent d3348b9 commit 5395039
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 4 deletions.
34 changes: 34 additions & 0 deletions docs/rules/no-let.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,44 @@ The default options:
```ts
const defaults = {
allowInForLoopInit: false,
allowLocalMutation: false
}
```

### `allowInForLoopInit`

If set, `let`s inside of for a loop initializer are allowed. This does not include for...of or for...in loops.

Examples of **correct** code for this rule:

```js
/* eslint functional/no-let: ["error", { "allowInForLoopInit": true } ] */

for (let i = 0; i < array.length; i++) {
}
```

Examples of **incorrect** code for this rule:

<!-- eslint-skip -->

```js
/* eslint functional/no-let: "error" */

for (let element of array) {
}
```

<!-- eslint-skip -->

```js
/* eslint functional/no-let: "error" */

for (let [index, element] of array.entries()) {
}
```

### `allowLocalMutation`

See the [allowLocalMutation](./options/allow-local-mutation.md) docs.
Expand Down
23 changes: 19 additions & 4 deletions src/rules/no-let.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,33 @@ import {
} from "~/common/ignore-options";
import type { RuleContext, RuleMetaData, RuleResult } from "~/util/rule";
import { createRule } from "~/util/rule";
import { inForLoopInitializer } from "~/util/tree";

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

// The options this rule can take.
type Options = AllowLocalMutationOption & IgnorePatternOption;
type Options = AllowLocalMutationOption &
IgnorePatternOption & {
readonly allowInForLoopInit: boolean;
};

// The schema for the rule options.
const schema: JSONSchema4 = [
deepmerge(allowLocalMutationOptionSchema, ignorePatternOptionSchema),
deepmerge(allowLocalMutationOptionSchema, ignorePatternOptionSchema, {
type: "object",
properties: {
allowInForLoopInit: {
type: "boolean",
},
},
additionalProperties: false,
}),
];

// The default options for the rule.
const defaultOptions: Options = {
allowInForLoopInit: false,
allowLocalMutation: false,
};

Expand Down Expand Up @@ -57,8 +70,10 @@ function checkVariableDeclaration(
options: Options
): RuleResult<keyof typeof errorMessages, Options> {
if (
node.kind !== "let" ||
shouldIgnoreLocalMutation(node, context, options) ||
shouldIgnorePattern(node, context, options)
shouldIgnorePattern(node, context, options) ||
(options.allowInForLoopInit && inForLoopInitializer(node))
) {
return {
context,
Expand All @@ -68,7 +83,7 @@ function checkVariableDeclaration(

return {
context,
descriptors: node.kind === "let" ? [{ node, messageId: "generic" }] : [],
descriptors: [{ node, messageId: "generic" }],
};
}

Expand Down
13 changes: 13 additions & 0 deletions src/util/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
isCallExpression,
isClassLike,
isDefined,
isForStatement,
isFunctionExpressionLike,
isFunctionLike,
isIdentifier,
Expand Down Expand Up @@ -53,6 +54,18 @@ export function inClass(node: TSESTree.Node): boolean {
return getAncestorOfType(isClassLike, node) !== null;
}

/**
* Test if the given node is in a for loop initializer.
*/
export function inForLoopInitializer(node: TSESTree.Node): boolean {
return (
getAncestorOfType(
(n, c): n is TSESTree.ForStatement => isForStatement(n) && n.init === c,
node
) !== null
);
}

/**
* Test if the given node is shallowly inside a `Readonly<{...}>`.
*/
Expand Down
6 changes: 6 additions & 0 deletions src/util/typeguard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,12 @@ export function isExpressionStatement(
return node.type === AST_NODE_TYPES.ExpressionStatement;
}

export function isForStatement(
node: TSESTree.Node
): node is TSESTree.ForStatement {
return node.type === AST_NODE_TYPES.ForStatement;
}

export function isFunctionDeclaration(
node: TSESTree.Node
): node is TSESTree.FunctionDeclaration {
Expand Down
4 changes: 4 additions & 0 deletions tests/rules/no-let/es6/valid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,10 @@ const tests: ReadonlyArray<ValidTestCase> = [
`,
optionsSet: [[{ ignorePattern: "Mutable$" }]],
},
{
code: `for (let x = 0; x < 1; x++);`,
optionsSet: [[{ allowInForLoopInit: true }]],
},
];

export default tests;

0 comments on commit 5395039

Please sign in to comment.