Repository: sindresorhus/eslint-plugin-unicorn (JavaScript)

Issue:
The `no-for-loop` rule doesn't preserve `let`
```js
const foo = input => {
	for (let i = 0; i < input.length; i++) {
		const c = input[i];
		i++;
		console.log(i, c);
	}

	return input;
};
```

Is auto-fixed into:

```js
const foo = input => {
	for (const [i, c] of input.entries()) {
		i++;
		console.log(i, c);
	}

	return input;
};
```

Which is a runtime error, since `i` is now `const`, but it cannot be because of `i++`.

---

// @futpib 


Discussion:
Actually, I don't think it should be auto-fixed at all, since the `i++` doesn't affect the `i` as it's reset for each iteration, 



Patch:
diff --git a/rules/no-for-loop.js b/rules/no-for-loop.js
index fad4669a75..eb7e58b503 100644
--- a/rules/no-for-loop.js
+++ b/rules/no-for-loop.js
@@ -220,8 +220,7 @@ const nodeContains = (ancestor, descendant) => {
 	return false;
 };
 
-const isIndexIdentifierUsedElsewhereInTheLoopBody = (forStatement, indexIdentifierName, bodyScope) => {
-	const indexVariable = resolveIdentifierName(indexIdentifierName, bodyScope);
+const isIndexVariableUsedElsewhereInTheLoopBody = (indexVariable, bodyScope) => {
 	const inBodyReferences = indexVariable.references.filter(reference => scopeContains(bodyScope, reference.from));
 
 	// One reference in the body would be the one in the element variable declaration like `const el = arr[i]`.
@@ -229,10 +228,14 @@ const isIndexIdentifierUsedElsewhereInTheLoopBody = (forStatement, indexIdentifi
 	return inBodyReferences.length > 1;
 };
 
-const someIdentifiersLeakOutOfTheLoop = (forStatement, identifiers, forScope, bodyScope) => {
-	return identifiers.some(name => {
-		const variable = resolveIdentifierName(name, bodyScope);
+const isIndexVariableAssignedToInTheLoopBody = (indexVariable, bodyScope) => {
+	return indexVariable.references
+		.filter(reference => scopeContains(bodyScope, reference.from))
+		.some(inBodyReference => inBodyReference.isWrite());
+};
 
+const someVariablesLeakOutOfTheLoop = (forStatement, variables, forScope) => {
+	return variables.some(variable => {
 		return !variable.references.every(reference => {
 			return scopeContains(forScope, reference.from) ||
 				nodeContains(forStatement, reference.identifier);
@@ -278,11 +281,15 @@ const create = context => {
 			const forScope = scopeManager.acquire(node);
 			const bodyScope = scopeManager.acquire(node.body);
 
-			const shouldFix = !someIdentifiersLeakOutOfTheLoop(node, [indexIdentifierName, elementIdentifierName], forScope, bodyScope);
+			const indexVariable = resolveIdentifierName(indexIdentifierName, bodyScope);
+			const elementVariable = resolveIdentifierName(elementIdentifierName, bodyScope);
+
+			const shouldFix = !someVariablesLeakOutOfTheLoop(node, [indexVariable, elementVariable], forScope) &&
+				!isIndexVariableAssignedToInTheLoopBody(indexVariable, bodyScope);
 
 			if (shouldFix) {
 				problem.fix = fixer => {
-					const shouldGenerateIndex = isIndexIdentifierUsedElsewhereInTheLoopBody(node, indexIdentifierName, bodyScope);
+					const shouldGenerateIndex = isIndexVariableUsedElsewhereInTheLoopBody(indexVariable, bodyScope);
 
 					const index = indexIdentifierName;
 					const element = elementIdentifierName;


Test patch:
diff --git a/test/no-for-loop.js b/test/no-for-loop.js
index 92154ceebc..ec565a19e5 100644
--- a/test/no-for-loop.js
+++ b/test/no-for-loop.js
@@ -85,10 +85,6 @@ ruleTester.run('no-for-loop', rule, {
 			let el = arr[i];
 			console.log(i, el);
 		}`,
-		`for (let i = 0; i < arr.length; ++i) {
-			const el = f(i);
-			console.log(i, el);
-		}`,
 
 		// Screwing with the body
 
@@ -97,6 +93,10 @@ ruleTester.run('no-for-loop', rule, {
 
 		// Screwing with element variable declaration
 
+		`for (let i = 0; i < arr.length; ++i) {
+			const el = f(i);
+			console.log(i, el);
+		}`,
 		`for (var j = 0; j < xs.length; j = j + 1) {
 			var x = xs[j], y = ys[j];
 			console.log(j, x, y);
@@ -184,6 +184,22 @@ ruleTester.run('no-for-loop', rule, {
 				console.log(i, el);
 			}
 			console.log(el);
+		`),
+
+		// Index is assigned to inside the loop body, should not fix (#252)
+		testCase(`
+			for (let i = 0; i < input.length; i++) {
+				const el = input[i];
+				i++;
+				console.log(i, el);
+			}
+		`),
+		testCase(`
+			for (let i = 0; i < input.length; i++) {
+				const el = input[i];
+				i = 4;
+				console.log(i, el);
+			}
 		`)
 	]
 });