Repository: ember-cli/eslint-plugin-ember
Pull request #1627 (2022-10-11 05:29:31)

Add `rejectBy` to `filter` autofixer for `no-array-prototype-extensions` rule
## Summary
Added auto-fixer for `no-array-prototype-extensions` which replaces `rejectBy` with `filter`.

## Description
Recently, the proposal to deprecate array prototype extensions has been approved and got merged. The plan is to add autoFixers to replace ember array prototype extensions with the native array methods. In this PR, an auto fixer has been added which will replace the ember array method `rejectBy` with the native array method `filter`.

## Testing
Modified test case to check if the right output is generated after the fix.

tgvrssanthosh: @bmish Please help review this PR?

Patch:
diff --git a/lib/rules/no-array-prototype-extensions.js b/lib/rules/no-array-prototype-extensions.js
index 1f70e915..254cda07 100644
--- a/lib/rules/no-array-prototype-extensions.js
+++ b/lib/rules/no-array-prototype-extensions.js
@@ -272,6 +272,39 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
 
       return [];
     }
+    case 'rejectBy': {
+      const fixes = [];
+
+      if (callArgs.length > 0 && callArgs.length < 3) {
+        const hasSecondArg = callArgs.length > 1;
+        const firstArg = callArgs[0];
+        const secondArg = callArgs[1];
+
+        // default to `get` if the `get` hasn't already been imported.
+        const importedGetName = options.importedGetName ?? 'get';
+
+        fixes.push(
+          fixer.replaceText(calleeProp, 'filter'),
+          // Replacing the content starting from open parenthesis to close parenthesis
+          // If the findBy contains two arguments, the property (first argument) value will be compared against the second argument
+          // If the filterBy contains only one argument, the property's truthy value is used to filter
+          fixer.replaceTextRange(
+            [openParenToken.range[0], closeParenToken.range[1]],
+            hasSecondArg
+              ? `(item => ${importedGetName}(item, ${sourceCode.getText(
+                  firstArg
+                )}) !== ${sourceCode.getText(secondArg)})`
+              : `(item => !${importedGetName}(item, ${sourceCode.getText(firstArg)}))`
+          )
+        );
+
+        // Add `get` import statement only if it is not imported already
+        if (!options.importedGetName) {
+          fixes.push(insertImportDeclaration(sourceCode, fixer, '@ember/object', importedGetName));
+        }
+      }
+      return fixes;
+    }
     case 'sortBy': {
       const fixes = [];
 


Test patch:
diff --git a/tests/lib/rules/no-array-prototype-extensions.js b/tests/lib/rules/no-array-prototype-extensions.js
index d74c0831..96ee4c74 100644
--- a/tests/lib/rules/no-array-prototype-extensions.js
+++ b/tests/lib/rules/no-array-prototype-extensions.js
@@ -555,10 +555,50 @@ import { get as g } from 'dummy';
       errors: [{ messageId: 'main', type: 'CallExpression' }],
     },
     {
+      // When unexpected number of arguments are passed, auto-fixer will not run
       code: 'something.rejectBy()',
       output: null,
       errors: [{ messageId: 'main', type: 'CallExpression' }],
     },
+    {
+      // Single argument is passed
+      code: 'something.rejectBy("abc")',
+      output: `import { get } from '@ember/object';
+something.filter(item => !get(item, "abc"))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // Two arguments are passed
+      code: 'something.rejectBy("abc", "def")',
+      output: `import { get } from '@ember/object';
+something.filter(item => get(item, "abc") !== "def")`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // When `get` is already imported from '@ember/object' package
+      code: `import { get } from '@ember/object';
+      something.rejectBy("abc")`,
+      output: `import { get } from '@ember/object';
+      something.filter(item => !get(item, "abc"))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // When `get` is already imported with alias from '@ember/object' package
+      code: `import { get as g } from '@ember/object';
+      something.rejectBy("abc")`,
+      output: `import { get as g } from '@ember/object';
+      something.filter(item => !g(item, "abc"))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // When `get` is already imported from a package other than '@ember/object'
+      code: `import { get as g } from '@custom/object';
+      something.rejectBy("abc")`,
+      output: `import { get } from '@ember/object';
+import { get as g } from '@custom/object';
+      something.filter(item => !get(item, "abc"))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
     {
       code: 'something.setEach()',
       output: null,