Repository: eslint-functional/eslint-plugin-functional
Pull request #936 (2025-02-24 00:02:05)

fix(immutable-data): make ignoreMapsAndSets option actually work

RebeccaStevens: * **#936** <a href="https://app.graphite.dev/github/pr/eslint-functional/eslint-plugin-functional/936?utm_source=stack-comment-icon" target="_blank"><img src="https://static.graphite.dev/graphite-32x32-black.png" alt="Graphite" width="10px" height="10px"/></a> 👈 <a href="https://app.graphite.dev/github/pr/eslint-functional/eslint-plugin-functional/936?utm_source=stack-comment-view-in-graphite" target="_blank">(View in Graphite)</a>
* `main`
<hr />

## How to use the Graphite Merge Queue

Add either label to this PR to merge it via the merge queue:
- <kbd>Status: Merge Queue</kbd> - adds this PR to the back of the merge queue
- <kbd>Status: Hotfix</kbd> - for urgent hot fixes, skip the queue and merge this PR next

You must have a Graphite account in order to use the merge queue. Sign up using [this link](https://app.graphite.dev/invite/github/eslint-functional?ref=merge-queue-instructions-in-stack-comment&prId=936).

<h2></h2>
<sub>

An organization admin has enabled the [Graphite Merge Queue](https://graphite.dev/docs/graphite-merge-queue) in this repository.

</sub>

<sub>

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

</sub>

<h2></h2>

This stack of pull requests is managed by <a href="https://graphite.dev?utm-source=stack-comment"><b>Graphite</b></a>. Learn more about <a href="https://stacking.dev/?utm_source=stack-comment">stacking</a>.
<!-- Current dependencies on/for this PR: -->
graphite-app[bot]: ### Merge activity

* **Feb 23, 7:02 PM EST**: A user added this pull request to the [Graphite merge queue](https://app.graphite.dev/merges?org=eslint-functional&repo=eslint-plugin-functional).


Patch:
diff --git a/src/rules/immutable-data.ts b/src/rules/immutable-data.ts
index 0675a1e..a95ea7f 100644
--- a/src/rules/immutable-data.ts
+++ b/src/rules/immutable-data.ts
@@ -526,7 +526,7 @@ function checkCallExpression(
     };
   }
 
-  const { ignoreImmediateMutation } = optionsToUse;
+  const { ignoreImmediateMutation, ignoreMapsAndSets } = optionsToUse;
 
   // Array mutation?
   if (
@@ -559,65 +559,67 @@ function checkCallExpression(
     }
   }
 
-  // Set mutation?
-  if (
-    setMutatorMethods.has(node.callee.property.name) &&
-    (!ignoreImmediateMutation || !isInChainCallAndFollowsNew(node.callee, context)) &&
-    isSetType(context, getTypeOfNode(node.callee.object, context))
-  ) {
-    if (ignoreNonConstDeclarations === false) {
-      return {
-        context,
-        descriptors: [{ node, messageId: "set" }],
-      };
-    }
-    const rootIdentifier = findRootIdentifier(node.callee.object);
+  if (!ignoreMapsAndSets) {
+    // Set mutation?
     if (
-      rootIdentifier === undefined ||
-      !isDefinedByMutableVariable(
-        rootIdentifier,
-        context,
-        (variableNode) =>
-          ignoreNonConstDeclarations === true ||
-          !ignoreNonConstDeclarations.treatParametersAsConst ||
-          shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern),
-      )
+      setMutatorMethods.has(node.callee.property.name) &&
+      (!ignoreImmediateMutation || !isInChainCallAndFollowsNew(node.callee, context)) &&
+      isSetType(context, getTypeOfNode(node.callee.object, context))
     ) {
-      return {
-        context,
-        descriptors: [{ node, messageId: "set" }],
-      };
+      if (ignoreNonConstDeclarations === false) {
+        return {
+          context,
+          descriptors: [{ node, messageId: "set" }],
+        };
+      }
+      const rootIdentifier = findRootIdentifier(node.callee.object);
+      if (
+        rootIdentifier === undefined ||
+        !isDefinedByMutableVariable(
+          rootIdentifier,
+          context,
+          (variableNode) =>
+            ignoreNonConstDeclarations === true ||
+            !ignoreNonConstDeclarations.treatParametersAsConst ||
+            shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern),
+        )
+      ) {
+        return {
+          context,
+          descriptors: [{ node, messageId: "set" }],
+        };
+      }
     }
-  }
 
-  // Map mutation?
-  if (
-    mapMutatorMethods.has(node.callee.property.name) &&
-    (!ignoreImmediateMutation || !isInChainCallAndFollowsNew(node.callee, context)) &&
-    isMapType(context, getTypeOfNode(node.callee.object, context))
-  ) {
-    if (ignoreNonConstDeclarations === false) {
-      return {
-        context,
-        descriptors: [{ node, messageId: "map" }],
-      };
-    }
-    const rootIdentifier = findRootIdentifier(node.callee.object);
+    // Map mutation?
     if (
-      rootIdentifier === undefined ||
-      !isDefinedByMutableVariable(
-        rootIdentifier,
-        context,
-        (variableNode) =>
-          ignoreNonConstDeclarations === true ||
-          !ignoreNonConstDeclarations.treatParametersAsConst ||
-          shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern),
-      )
+      mapMutatorMethods.has(node.callee.property.name) &&
+      (!ignoreImmediateMutation || !isInChainCallAndFollowsNew(node.callee, context)) &&
+      isMapType(context, getTypeOfNode(node.callee.object, context))
     ) {
-      return {
-        context,
-        descriptors: [{ node, messageId: "map" }],
-      };
+      if (ignoreNonConstDeclarations === false) {
+        return {
+          context,
+          descriptors: [{ node, messageId: "map" }],
+        };
+      }
+      const rootIdentifier = findRootIdentifier(node.callee.object);
+      if (
+        rootIdentifier === undefined ||
+        !isDefinedByMutableVariable(
+          rootIdentifier,
+          context,
+          (variableNode) =>
+            ignoreNonConstDeclarations === true ||
+            !ignoreNonConstDeclarations.treatParametersAsConst ||
+            shouldIgnorePattern(variableNode, context, ignoreIdentifierPattern, ignoreAccessorPattern),
+        )
+      ) {
+        return {
+          context,
+          descriptors: [{ node, messageId: "map" }],
+        };
+      }
     }
   }
 


Test patch:
diff --git a/tests/rules/immutable-data/map.test.ts b/tests/rules/immutable-data/map.test.ts
index dd7f425..926ee0c 100644
--- a/tests/rules/immutable-data/map.test.ts
+++ b/tests/rules/immutable-data/map.test.ts
@@ -28,6 +28,22 @@ describe(name, () => {
       expect(invalidResult.messages).toMatchSnapshot();
     });
 
+    it("doesn't report mutating map methods when ignoring maps and sets", () => {
+      valid({
+        code: dedent`
+          const x = new Map([[5, 6]]);
+          x.set(4, 8);
+          x.delete(4);
+          x.clear();
+        `,
+        options: [
+          {
+            ignoreMapsAndSets: true,
+          },
+        ],
+      });
+    });
+
     it("doesn't report non-mutating map methods", () => {
       valid(dedent`
         const x = new Map([[5, 6]]);
diff --git a/tests/rules/immutable-data/set.test.ts b/tests/rules/immutable-data/set.test.ts
index 6cda1bf..55c18d4 100644
--- a/tests/rules/immutable-data/set.test.ts
+++ b/tests/rules/immutable-data/set.test.ts
@@ -28,6 +28,22 @@ describe(name, () => {
       expect(invalidResult.messages).toMatchSnapshot();
     });
 
+    it("doesn't report mutating set methods when ignoring maps and sets", () => {
+      valid({
+        code: dedent`
+          const x = new Set([5, 6]);
+          x.add(4);
+          x.delete(4);
+          x.clear();
+        `,
+        options: [
+          {
+            ignoreMapsAndSets: true,
+          },
+        ],
+      });
+    });
+
     it("doesn't report non-mutating set methods", () => {
       valid(dedent`
         const x = new Set([5, 6]);