Skip to content

fix: detect Map and Set mutation methods - #354

Open
electrohyun wants to merge 1 commit into
eslint-community:mainfrom
electrohyun:fix/map-set-mutations
Open

fix: detect Map and Set mutation methods#354
electrohyun wants to merge 1 commit into
eslint-community:mainfrom
electrohyun:fix/map-set-mutations

Conversation

@electrohyun

Copy link
Copy Markdown

What is the purpose of this pull request?

This PR fixes an issue where getStaticValue() statically evaluates .size based on the initial state without detecting mutating method calls on a Set or Map.

In summary:

  1. In const set = new Set(); set.add("a"); set.size, the actual value of set.size is 1.
  2. However, the existing implementation does not recognize set.add() as a mutation. Therefore, it returns { value: 0 }.
  3. After the object has been mutated, its current value cannot be determined from its initial value alone, so getStaticValue() should return null.

What changes did you make? (Give an overview)

I kept the existing detection of mutating array methods unchanged and added the Set and Map mutation checks as a separate condition.

I changed hasMutationInProperty() to receive the static value already evaluated from the variable's initializer. This allows it to detect each mutating method only when the method's receiver is the variable itself and its static initial value is an actual Set or Map.

  • Set: add, delete, clear
  • Map: set, delete, clear

As a result, when a direct mutating method call on a Set or Map is detected, getStaticValue() returns null instead of statically evaluating the current value from the initial state alone.

I added regression tests that evaluate .size after calls to Set.prototype.add(), Set.prototype.delete(), Set.prototype.clear(), Map.prototype.set(), Map.prototype.delete(), and Map.prototype.clear().

I also added tests to ensure that ordinary objects with non-mutating methods named add, clear, delete, or set continue to be statically evaluated as before. The tests verify that a mutating Set method called through a statically evaluated computed property is detected, while a non-mutating method on an ordinary object called in the same way is not. They also verify that existing static evaluation of non-mutating methods such as Set.prototype.has() is preserved.

I have summarized below why the Set and Map mutation checks do not rely on method names alone. A name-only check could incorrectly treat ordinary objects with methods of the same name as mutated.


Why the checks do not rely on method names alone

I initially considered generalizing the existing array mutation method check and adding add, clear, delete, and set to a name-based list of mutating methods. However, this approach does not check the receiver's actual type, so it would also treat an ordinary object like the following as mutated.

const object = { value: 1, set: String }
object.set(2)
object.value // getStaticValue() should return { value: 1 }, but a name-only check would return null

object.set(2) does not mutate object. The existing implementation evaluates object.value as { value: 1 }, but checking only the method name would treat set as a mutation and return null. To avoid this unnecessary behavior change, I kept the existing array detection unchanged and limited the new mutation detection to cases where the static initial value is an actual Set or Map and the variable itself is the direct receiver.

Related Issues

Fixes #353

Is there anything you'd like reviewers to focus on?

This change only handles cases where the static initial value is an actual Set or Map and the variable itself is the direct receiver. Indirect mutations through an alias, a function argument, or calls such as Set.prototype.add.call() are outside the scope of this change.

Disclosure: I'm a participant of open source contribution program OSSCA

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getStaticValue ignores Set/Map mutation and folds .size to the initializer value

1 participant