Repository: ember-cli/eslint-plugin-ember
Pull request #1618 (2022-10-07 16:58:15)

Add jQuery codemod links
And links to each of the rules.


Patch:
diff --git a/docs/rules/jquery-ember-run.md b/docs/rules/jquery-ember-run.md
index 4cd352a0..e1191610 100644
--- a/docs/rules/jquery-ember-run.md
+++ b/docs/rules/jquery-ember-run.md
@@ -29,3 +29,8 @@ $('#something-rendered-by-jquery-plugin').on(
   bind(this, this._handlerActionFromController)
 );
 ```
+
+## Related
+
+- [no-global-jquery](./no-global-jquery.md)
+- [no-jquery](./no-jquery.md)
diff --git a/docs/rules/no-global-jquery.md b/docs/rules/no-global-jquery.md
index 8e5cfe19..671a3a28 100644
--- a/docs/rules/no-global-jquery.md
+++ b/docs/rules/no-global-jquery.md
@@ -36,3 +36,8 @@ export default Component.extend({
   }
 });
 ```
+
+## Related
+
+- [jquery-ember-run](./jquery-ember-run.md)
+- [no-jquery](./no-jquery.md)
diff --git a/docs/rules/no-jquery.md b/docs/rules/no-jquery.md
index bef4bd9e..39c43b33 100644
--- a/docs/rules/no-jquery.md
+++ b/docs/rules/no-jquery.md
@@ -6,8 +6,7 @@ This rule attempts to catch and prevent any usage of jQuery.
 
 ## Rule Details
 
-If you want to remove jQuery this rule can help you by warning you of any usage of jQuery
-in your app.
+If you want to remove jQuery, this rule can help you by warning you of any usage of jQuery in your app.
 
 That includes:
 
@@ -16,9 +15,6 @@ That includes:
 - The global `$`
 - `Ember.$` or `const { $ } = Ember;`
 
-For replacing `this.$` on components you can use the native DOM counterpart `this.element`
-For replacing `this.$` on tests, check [ember-native-dom-helpers](https://github.com/cibernox/ember-native-dom-helpers)
-
 ## Examples
 
 Examples of **incorrect** code for this rule:
@@ -59,7 +55,23 @@ export default Component.extend({
 });
 ```
 
+## Migration
+
+For replacing `this.$` on components, you can use the native DOM counterpart `this.element`.
+
+For replacing `this.$` on tests, check [ember-native-dom-helpers](https://github.com/cibernox/ember-native-dom-helpers).
+
+Codemods that could help:
+
+- [ember-3x-codemods](https://github.com/ember-codemods/ember-3x-codemods)
+- [ember-test-helpers-codemod](https://github.com/ember-codemods/ember-test-helpers-codemod)
+
 ## RFCs
 
 - [Make jQuery optional](https://github.com/emberjs/rfcs/blob/master/text/0294-optional-jquery.md)
 - [Remove jQuery by default](https://github.com/emberjs/rfcs/blob/master/text/0386-remove-jquery.md)
+
+## Related
+
+- [jquery-ember-run](./jquery-ember-run.md)
+- [no-global-jquery](./no-global-jquery.md)
diff --git a/lib/rules/no-array-prototype-extensions.js b/lib/rules/no-array-prototype-extensions.js
index f9b19764..a056e317 100644
--- a/lib/rules/no-array-prototype-extensions.js
+++ b/lib/rules/no-array-prototype-extensions.js
@@ -161,20 +161,21 @@ function variableNameToWords(name) {
  * @returns {Object|[]}
  */
 function applyFix(callExpressionNode, fixer, context, options = {}) {
-  const propertyName = callExpressionNode.callee.property.name;
+  const calleeProp = callExpressionNode.callee.property;
+  const propertyName = calleeProp.name;
+  const calleeObj = callExpressionNode.callee.object;
+  const callArgs = callExpressionNode.arguments;
+  const sourceCode = context.getSourceCode();
 
   switch (propertyName) {
     case 'any': {
-      return fixer.replaceText(callExpressionNode.callee.property, 'some');
+      return fixer.replaceText(calleeProp, 'some');
     }
     case 'compact': {
-      const calleePropertyNode = callExpressionNode.callee.property;
-      const sourceCode = context.getSourceCode();
-      const callArgs = callExpressionNode.arguments;
       const fixes = [];
 
       // Get the open parenthesis immediately after the callee name
-      const openParenToken = sourceCode.getTokenAfter(calleePropertyNode, {
+      const openParenToken = sourceCode.getTokenAfter(calleeProp, {
         filter(token) {
           return token.type === TOKEN_TYPES.PUNCTUATOR && token.value === '(';
         },
@@ -188,7 +189,7 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
 
       if (openParenToken && closeParenToken && callArgs.length === 0) {
         fixes.push(
-          fixer.replaceText(calleePropertyNode, 'filter'),
+          fixer.replaceText(calleeProp, 'filter'),
           // Replacing the content starting from open parenthesis to close parenthesis
           fixer.replaceTextRange(
             [openParenToken.range[0], closeParenToken.range[1]],
@@ -201,17 +202,14 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
     case 'filterBy':
     case 'findBy': {
       const fixes = [];
-      const callArgs = callExpressionNode.arguments;
 
       if (callArgs.length > 0 && callArgs.length < 3) {
-        const sourceCode = context.getSourceCode();
         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';
-        const calleeProp = callExpressionNode.callee.property;
 
         // Get the open parenthesis immediately after the callee name
         const openParenToken = sourceCode.getTokenAfter(calleeProp, {
@@ -248,10 +246,6 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
       return fixes;
     }
     case 'objectAt': {
-      const calleeObj = callExpressionNode.callee.object;
-      const callArgs = callExpressionNode.arguments;
-      const sourceCode = context.getSourceCode();
-
       if (callArgs.length === 1) {
         return [
           fixer.insertTextBefore(
@@ -265,10 +259,6 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
       return [];
     }
     case 'toArray': {
-      const calleeObj = callExpressionNode.callee.object;
-      const callArgs = callExpressionNode.arguments;
-      const sourceCode = context.getSourceCode();
-
       if (callArgs.length === 0) {
         return [
           fixer.insertTextBefore(callExpressionNode, `[...${sourceCode.getText(calleeObj)}]`),
@@ -278,6 +268,43 @@ function applyFix(callExpressionNode, fixer, context, options = {}) {
 
       return [];
     }
+    case 'without': {
+      // Get the open parenthesis immediately after the callee name
+      const openParenToken = sourceCode.getTokenAfter(calleeProp, {
+        filter(token) {
+          return token.type === TOKEN_TYPES.PUNCTUATOR && token.value === '(';
+        },
+      });
+      // Get the close parenthesis from the end of the callExpressionNode
+      const closeParenToken = sourceCode.getLastToken(callExpressionNode, {
+        filter(token) {
+          return token.type === TOKEN_TYPES.PUNCTUATOR && token.value === ')';
+        },
+      });
+
+      if (callArgs.length === 1) {
+        const argText = sourceCode.getText(callArgs[0]);
+        const calleeObjText = sourceCode.getText(calleeObj);
+
+        return [
+          // As per https://api.emberjs.com/ember/release/classes/EmberArray/methods/mapBy?anchor=without
+          // when the passed value doesn't not exist in the array, it returns the original array
+          // Hence we first check for the existence of the passed value in the array before calling filter on array
+          // Used indexOf instead of includes since v3.x of ember is committed to supporting IE11
+          // as per the ember browser support policy (https://emberjs.com/browser-support/)
+          // TODO: Switch to includes once Ember v3 LTS support ends: https://emberjs.com/releases/lts/
+          fixer.replaceText(calleeProp, `indexOf(${argText}) > -1 ? ${calleeObjText}.filter`),
+          fixer.insertTextAfter(closeParenToken, ` : ${calleeObjText}`),
+          // Replacing the content starting from open parenthesis to close parenthesis
+          fixer.replaceTextRange(
+            [openParenToken.range[0], closeParenToken.range[1]],
+            `(item => item !== ${argText})`
+          ),
+        ];
+      }
+
+      return [];
+    }
     default: {
       return [];
     }


Test patch:
diff --git a/tests/lib/rules/no-array-prototype-extensions.js b/tests/lib/rules/no-array-prototype-extensions.js
index 6acfa612..7f5937ce 100644
--- a/tests/lib/rules/no-array-prototype-extensions.js
+++ b/tests/lib/rules/no-array-prototype-extensions.js
@@ -559,10 +559,28 @@ import { get as g } from 'dummy';
       errors: [{ messageId: 'main', type: 'CallExpression' }],
     },
     {
+      // When unexpected number of params are passed, we will skip auto-fixing
       code: 'something.without()',
       output: null,
       errors: [{ messageId: 'main', type: 'CallExpression' }],
     },
+    {
+      // When unexpected number of params are passed, we will skip auto-fixing
+      code: 'something.without(1, 2)',
+      output: null,
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      code: 'something.without(1)',
+      output: 'something.indexOf(1) > -1 ? something.filter(item => item !== 1) : something',
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
+    {
+      code: 'something.somethingElse.without(1)',
+      output:
+        'something.somethingElse.indexOf(1) > -1 ? something.somethingElse.filter(item => item !== 1) : something.somethingElse',
+      errors: [{ messageId: 'main', type: 'CallExpression' }],
+    },
     {
       code: 'something.lastObject',
       output: null,