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

Rule proposal: `proper-object-iterable-methods`

Open fisker opened this issue 3 years ago • 12 comments

Fail

// Should use `Object.values`
for (const key of Object.keys(foo)) {
	bar(foo[key]);
}
// Should use `Object.entries`
for (const key of Object.keys(foo)) {
	bar(foo[key], key);
}
// Should use `Object.values`
const foo = Object.keys(bar).map(key => baz(bar[key]));
// Should use `Object.entries`
const foo = Object.keys(bar).map(key => baz(bar[key], key));
// Should use `Object.keys`
for (const [key] of Object.entries(foo)) {
	bar(key);
}

Pass

for (const key of Object.keys(foo)) {
	foo[key] = 'new value';
	delete foo[key];
	foo[key]++;
}

fisker avatar Jan 25 '21 07:01 fisker

Rule name: prefer-object-entries-or-values? Any better suggestions? Or should it be split into two rules?

sindresorhus avatar Jan 25 '21 07:01 sindresorhus

I think we should also enforce Object.keys over Object.entries in this case

for (const [key] of Object.entries(foo)) {
	bar(key);
}

So better come up a better name cover all them.

fisker avatar Jan 25 '21 07:01 fisker

proper-object-iterable-methods?

fisker avatar Jan 25 '21 07:01 fisker

proper-object-iterable-methods?

Yeah. Sounds ok. I cannot come up with something better.

sindresorhus avatar Jan 25 '21 18:01 sindresorhus

This is now accepted.

sindresorhus avatar Feb 16 '21 16:02 sindresorhus

a (presumably) more complicated case that could also be detected:

// fail
const foo = Object.keys(myObj).reduce((acc, key) => {
  const value = myObj[key];
  acc[key] = value + 1;
  return acc;
}, {})

// pass
const foo = Object.fromEntries(
  Object.entries(myObj).map(([key, value]) => [key, value + 1])
)

ljosberinn avatar Apr 08 '21 13:04 ljosberinn

While I was doing a refactoring to get rid of a for..in. I wondered if this rule should handle this case

// BEFORE
for (const key in object) {
  const value = object[key]
  f(value)
}

// AFTER
for (const [key, value] of Object.entries(object)) {
  f(value)
}

According to MDN,

Object.entries() is the same as iterating with a for...in loop, except that a for...in loop enumerates properties in the prototype chain as well.

The answer is no. This transformation is not generally equivalent. This transformation is only valid for plain objects.

Pyrolistical avatar Feb 07 '22 22:02 Pyrolistical

@Pyrolistical presumably you also have the guard-for-in rule so:

  • the "before" version should not exist
  • the gated version should be equivalent and thus fixable

fregante avatar May 30 '22 07:05 fregante

@fregante for..in is safe if you know its an object literal. I don't use classes, but you are right this can cause problems to those who do.

> for (const key in {x: 42}) { console.log(key) }
x

Pyrolistical avatar May 30 '22 17:05 Pyrolistical

for..in is safe if you know its an object literal.

No, it's not.

Object.prototype.map = () => {}
for (const key in {x: 42}) { console.log(key) }

fisker avatar May 30 '22 17:05 fisker

@fisker that's fair.

installs no-extend-native

Pyrolistical avatar May 30 '22 17:05 Pyrolistical

<script src="lib-add-method-to-object-prototype-but-forgot-set-non-enumerable.js">

import "lib-add-method-to-object-prototype-but-forgot-set-non-enumerable"

fisker avatar May 30 '22 17:05 fisker