Repository: ember-cli/eslint-plugin-ember (js)

Issue:
False positives with `no-array-prototype-extensions` rule
Further to #1534 there are additional false positives when this rule is run against [ember-file-upload](https://github.com/adopted-ember-addons/ember-file-upload/runs/7440106845?check_suite_focus=true).

- `.clear()` on a `Set` (probably on a Map, too)
- `RSVP.reject()`

If I have time tomorrow I'll open a PR to address.

Pull request:
Fix false positives with simple Set/Map-initialized objects in `no-array-prototype-extensions` rule
(partially) fixes #1537.

Patch:
diff --git a/lib/rules/no-array-prototype-extensions.js b/lib/rules/no-array-prototype-extensions.js
index 832e3b17..0a63f811 100644
--- a/lib/rules/no-array-prototype-extensions.js
+++ b/lib/rules/no-array-prototype-extensions.js
@@ -1,6 +1,6 @@
 'use strict';
 
-const { getName } = require('../utils/utils');
+const { getName, getNodeOrNodeFromVariable } = require('../utils/utils');
 
 const ERROR_MESSAGE = "Don't use Ember's array prototype extensions";
 
@@ -62,6 +62,9 @@ const KNOWN_NON_ARRAY_FUNCTION_CALLS = new Set([
   'window.Promise.reject',
   'Promise.reject',
 
+  // RSVP.reject
+  'RSVP.reject',
+
   // *storage.clear()
   'window.localStorage.clear',
   'window.sessionStorage.clear',
@@ -80,6 +83,19 @@ const KNOWN_NON_ARRAY_OBJECTS = new Set([
   'jquery',
 ]);
 
+const KNOWN_NON_ARRAY_CLASSES = new Set([
+  // These Set/Map data structure classes have an overlapping clear() method.
+  'Set',
+  'Map',
+  'WeakSet',
+  'WeakMap',
+  // https://github.com/tracked-tools/tracked-built-ins
+  'TrackedSet',
+  'TrackedMap',
+  'TrackedWeakSet',
+  'TrackedWeakMap',
+]);
+
 //----------------------------------------------------------------------------------------------
 // General rule - Don't use Ember's array prototype extensions like .any(), .pushObject() or .firstObject
 //----------------------------------------------------------------------------------------------
@@ -102,6 +118,9 @@ module.exports = {
   },
 
   create(context) {
+    const sourceCode = context.getSourceCode();
+    const { scopeManager } = sourceCode;
+
     return {
       /**
        * Cover cases when `EXTENSION_METHODS` is getting called.
@@ -132,6 +151,17 @@ module.exports = {
           return;
         }
 
+        const nodeInitializedTo = getNodeOrNodeFromVariable(node.callee.object, scopeManager);
+        if (
+          nodeInitializedTo.type === 'NewExpression' &&
+          nodeInitializedTo.callee.type === 'Identifier' &&
+          KNOWN_NON_ARRAY_CLASSES.has(nodeInitializedTo.callee.name)
+        ) {
+          // Ignore when we can tell the variable was initialized to an instance of a non-array class.
+          // Example: const foo = new Set();
+          return;
+        }
+
         if (EXTENSION_METHODS.has(node.callee.property.name)) {
           context.report({ node, messageId: 'main' });
         }


Test patch:
diff --git a/tests/lib/rules/no-array-prototype-extensions.js b/tests/lib/rules/no-array-prototype-extensions.js
index 1cece71f..ad490dad 100644
--- a/tests/lib/rules/no-array-prototype-extensions.js
+++ b/tests/lib/rules/no-array-prototype-extensions.js
@@ -49,6 +49,10 @@ ruleTester.run('no-array-prototype-extensions', rule, {
     'Promise.reject("some reason");',
     'reject();',
 
+    // Global non-array class (RSVP.reject)
+    'RSVP.reject();',
+    'RSVP.reject("some reason");',
+
     // Global non-array class (*storage.clear)
     'window.localStorage.clear();',
     'window.sessionStorage.clear();',
@@ -74,6 +78,16 @@ ruleTester.run('no-array-prototype-extensions', rule, {
     '$( "li" ).toArray();',
     'jQuery( "li" ).toArray();',
     'jquery( "li" ).toArray();',
+
+    // Non-array classes with clear() method
+    'const foo = new Set(); foo.clear();',
+    'const foo = new WeakSet(); foo.clear();',
+    'const foo = new Map(); foo.clear();',
+    'const foo = new WeakMap(); foo.clear();',
+    'const foo = new TrackedMap(); foo.clear();',
+    'const foo = new TrackedSet(); foo.clear();',
+    'const foo = new TrackedWeakMap(); foo.clear();',
+    'const foo = new TrackedWeakSet(); foo.clear();',
   ],
   invalid: [
     {