eslint-plugin-unicorn icon indicating copy to clipboard operation
eslint-plugin-unicorn copied to clipboard

Rule proposal: `prefer-array-map`

Open Pyrolistical opened this issue 2 years ago • 10 comments

Description

Often no-for-loop results in something that could be simplified down to usage of Array.prototype.map()

This is an alternative to https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1714 and only one of these rules should be implemented.

I would only allow this rule to match only "simple" for..of cases as in these examples.

Fail

const result = []
for (const element of array) {
  result.push(transform(element))
}
const result = []
for (const [index, element] of array.entries()) {
  result[index] = transform(element)
}
const result = []
for (const [index, element] of array.entries()) {
  result.push(anotherArray[index](element))
}

Pass

const result = array.map((element) => transform(element))
const result = array.map((element, index) => anotherArray[index](element))

Pyrolistical avatar Feb 02 '22 23:02 Pyrolistical

Hmm, this rule could be problematic for stuff beyond arrays. for..of isn't just for arrays, its generally for iterables https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols

So this transformation is only valid if we know x in for (... of x) { is an Array

Pyrolistical avatar Feb 03 '22 00:02 Pyrolistical

I would wait on https://github.com/tc39/proposal-iterator-helpers

sindresorhus avatar Feb 03 '22 03:02 sindresorhus

This should work for for..await..of, once Array.fromAsync is ready.

fisker avatar Feb 03 '22 11:02 fisker

@fisker interesting. maybe that's a different rule, prefer-array-from and uses https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from

the neat thing about that is it has a built in map function, so it already works for both arrays and iterators.

related is prefer-spread which transform Array.from to spread

Pyrolistical avatar Feb 03 '22 19:02 Pyrolistical

I think I'm against this. for..of is generally superiour because it allows flow control statements. Array#map has its uses for simple one-liners, but I would not recommend it in the general sense as a replacement of for..of.

silverwind avatar Feb 04 '22 18:02 silverwind

Found one in our codebase

https://github.com/sindresorhus/eslint-plugin-unicorn/blob/ba9d53cd522e641d9b8da7ce495ed0110b8ce33a/test/package.mjs#L100-L111

fisker avatar Feb 09 '22 08:02 fisker

Another case if there is more than 1 argument in Array#push method, should be reported by prefer-array-flat-map.

dimaMachina avatar Feb 17 '22 00:02 dimaMachina

One exception: when the loop content accesses the array for other reasons (e.g. to look back). Random example:

const result = [];
for (const item of array) {
	if (result.includes(item)) { // `result` array access
		console.error('Found duplicate!!11')
	} else {
		result.push(item * 2)
	}
}

fregante avatar Mar 05 '22 09:03 fregante

@fregante yep, the rule wouldn't apply in this case as it access the result before .map completes. However, that example could be rewritten as an .reduce

Pyrolistical avatar Mar 05 '22 20:03 Pyrolistical

I would only allow this rule to match only "simple" for..of cases as in these examples.

Maybe a single condition could also be wrapped. From:

const result = []
for (const element of array) {
	if (element > 4)
		result.push(transform(element))
}

to:

const result = array
	.filter(element => element > 4)
	.map((element) => transform(element))

fregante avatar Mar 10 '22 09:03 fregante