Repository: ember-cli/eslint-plugin-ember
Pull request #1631 (2022-10-14 11:22:57)

Add `invoke` to `map` autofixer for `no-array-prototype-extensions` rule
## Summary
Added auto-fixer for `no-array-prototype-extensions` which replaces `invoke` with `map`.

## 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 `invoke` with the native array method `map`.

## Testing
Modified test case to check if the right output is generated after the fix.

tgvrssanthosh: @bmish Can you please help review the PR?

Patch:
diff --git a/lib/rules/no-array-prototype-extensions.js b/lib/rules/no-array-prototype-extensions.js
index 5dc4f2d0..efdcfd3a 100644
--- a/lib/rules/no-array-prototype-extensions.js
+++ b/lib/rules/no-array-prototype-extensions.js
@@ -255,6 +255,35 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
 
       return fixes;
     }
+    case 'invoke': {
+      const fixes = [];
+
+      if (callArgs.length > 0) {
+        const argText = sourceCode.getText(callArgs[0]);
+        const restOfArgs = callArgs
+          .slice(1)
+          .map((arg) => sourceCode.getText(arg))
+          .join(', ');
+
+        // default to `get` if the `get` hasn't already been imported.
+        const importedGetName = options.importedGetName ?? 'get';
+
+        fixes.push(
+          fixer.replaceText(calleeProp, 'map'),
+          // Replacing the content starting from open parenthesis to close parenthesis
+          fixer.replaceTextRange(
+            [openParenToken.range[0], closeParenToken.range[1]],
+            `(item => ${importedGetName}(item, ${argText})?.(${restOfArgs}))`
+          )
+        );
+
+        // 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 'mapBy': {
       if (callArgs.length === 1) {
         const argText = sourceCode.getText(callArgs[0]);


Test patch:
diff --git a/tests/lib/rules/no-array-prototype-extensions.js b/tests/lib/rules/no-array-prototype-extensions.js
index 85c5525e..85f0ae32 100644
--- a/tests/lib/rules/no-array-prototype-extensions.js
+++ b/tests/lib/rules/no-array-prototype-extensions.js
@@ -466,10 +466,48 @@ 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.invoke()',
       output: null,
       errors: [{ messageId: 'main', type: 'CallExpression' }],
     },
+    {
+      code: 'something.invoke("abc")',
+      output: `import { get } from '@ember/object';
+something.map(item => get(item, "abc")?.())`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      code: 'something.invoke("abc", "def")',
+      output: `import { get } from '@ember/object';
+something.map(item => get(item, "abc")?.("def"))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // When `get` method is already imported from '@ember/object' package
+      code: `import { get } from '@ember/object';
+      something.invoke('abc', {})`,
+      output: `import { get } from '@ember/object';
+      something.map(item => get(item, 'abc')?.({}))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // When `get` method is already imported with alias from '@ember/object' package
+      code: `import { get as g } from '@ember/object';
+      something.invoke('abc', {})`,
+      output: `import { get as g } from '@ember/object';
+      something.map(item => g(item, 'abc')?.({}))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      // When `get` method is already imported from a package other than '@ember/object'
+      code: `import { get as g } from '@custom/object';
+      something.invoke('abc', {}, 'test', true)`,
+      output: `import { get } from '@ember/object';
+import { get as g } from '@custom/object';
+      something.map(item => get(item, 'abc')?.({}, 'test', true))`,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
     {
       // When unexpected number of arguments are passed, auto-fixer will not run
       code: 'something.isAny()',