Repository: ember-cli/eslint-plugin-ember
Pull request #1512 (2022-07-14 19:07:13)

Update `avoid-leaking-state-in-ember-objects` rule to augment instead of replace default config
Right now, it's inconvenient and error-prone how users have to import the default config in order to augment it for the [avoid-leaking-state-in-ember-objects](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/avoid-leaking-state-in-ember-objects.md) rule. Augmenting the default config is the behavior that all or most users want (I'm unaware why anyone would ever NOT want to augment it), so we can simplify the rule by doing this automatically. Please file an issue if this doesn't meet your needs.



Before:

```js
const {
  DEFAULT_IGNORED_PROPERTIES
} = require('eslint-plugin-ember/lib/rules/avoid-leaking-state-in-ember-objects');

module.exports = {
  rules: {
    'ember/avoid-leaking-state-in-ember-objects': [
      'error',
      [...DEFAULT_IGNORED_PROPERTIES, 'array', 'of', 'ignored', 'properties']
    ]
  }
};
```

Another before example:

```js
const { DEFAULT_IGNORED_PROPERTIES } = require('eslint-plugin-ember/lib/rules/avoid-leaking-state-in-ember-objects')
// This comes directly from the documentation for Ember Data URL Templates:
// https://github.com/amiel/ember-data-url-templates/wiki/Custom-urlSegments
'ember/avoid-leaking-state-in-ember-objects': ['error', ['urlSegments'].concat(DEFAULT_IGNORED_PROPERTIES)],
```

After this change (no behavior change):

```js
module.exports = {
  rules: {
    'ember/avoid-leaking-state-in-ember-objects': [
      'error',
      ['array', 'of', 'ignored', 'properties']
    ]
  }
};
```

Eliminating the need to import from this rule will make it easier for us to strictly-define our Node API using package.json `exports` in the next major release (meaning we will prevent users from importing data from arbitrary files inside this plugin).

Part of v11 release (#1169).


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c47a8624..fe724ac2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -17,7 +17,7 @@ jobs:
     strategy:
       matrix:
         os: [ ubuntu, windows ]
-        node-version: [10.x, 12.x, 14.x, 16.x, 18.x]
+        node-version: [14.x, 16.x, 18.x]
 
     steps:
     - uses: actions/checkout@v3
diff --git a/README.md b/README.md
index 1108cf8b..d1ec481a 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
 ## ❗️Requirements
 
 - [ESLint](https://eslint.org/) `>= 6`
-- [Node.js](https://nodejs.org/) `10.* || 12.* || >= 14`
+- [Node.js](https://nodejs.org/) `14.* || 16.* || >= 18`
 
 ## 🚀 Usage
 
diff --git a/docs/rules/avoid-leaking-state-in-ember-objects.md b/docs/rules/avoid-leaking-state-in-ember-objects.md
index 784ebf64..ccbd45aa 100644
--- a/docs/rules/avoid-leaking-state-in-ember-objects.md
+++ b/docs/rules/avoid-leaking-state-in-ember-objects.md
@@ -62,22 +62,14 @@ export default class FooComponent extends Component {
 
 ## Configuration
 
-If you have properties where you know that the shared state won't be a problem (for example, read-only configuration values),
-you can configure this rule to ignore specific properties. Ember already does this internally for properties such as `actions`.
-
-The configuration of this rule takes a second argument that is a list of property names to ignore. This plugin makes the default
-list of ignored properties available for you to extend from, as follows:
+If you have custom properties where you know that the shared state won't be a problem (for example, read-only configuration values), you can configure this rule to ignore them by passing the property names to the rule as follows. Note that this rule will always automatically ignore known-safe Ember properties such as `actions`.
 
 ```js
-const {
-  DEFAULT_IGNORED_PROPERTIES
-} = require('eslint-plugin-ember/lib/rules/avoid-leaking-state-in-ember-objects');
-
 module.exports = {
   rules: {
     'ember/avoid-leaking-state-in-ember-objects': [
       'error',
-      [...DEFAULT_IGNORED_PROPERTIES, 'array', 'of', 'ignored', 'properties']
+      ['array', 'of', 'ignored', 'properties']
     ]
   }
 };
diff --git a/docs/rules/no-get.md b/docs/rules/no-get.md
index 3e4592b2..fb89757f 100644
--- a/docs/rules/no-get.md
+++ b/docs/rules/no-get.md
@@ -92,8 +92,8 @@ export default EmberObject.extend({
 This rule takes an optional object containing:
 
 * `boolean` -- `ignoreGetProperties` -- whether the rule should ignore `getProperties` (default `false`)
-* `boolean` -- `ignoreNestedPaths` -- whether the rule should ignore `this.get('some.nested.property')` (default `false`)
-* `boolean` -- `useOptionalChaining` -- whether the rule should use the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) `?.` to autofix nested paths such as `this.get('some.nested.property')` to `this.some?.nested?.property` (when this option is off, these nested paths won't be autofixed at all) (default `false`)
+* `boolean` -- `ignoreNestedPaths` -- whether the rule should ignore `this.get('some.nested.property')` (can't be enabled at the same time as `useOptionalChaining`) (default `false`)
+* `boolean` -- `useOptionalChaining` -- whether the rule should use the [optional chaining operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) `?.` to autofix nested paths such as `this.get('some.nested.property')` to `this.some?.nested?.property` (when this option is off, these nested paths won't be autofixed at all) (default `true`)
 * `boolean` -- `catchSafeObjects` -- whether the rule should catch non-`this` imported usages like `get(foo, 'bar')` (default `true`)
 * `boolean` -- `catchUnsafeObjects` -- whether the rule should catch non-`this` usages like `foo.get('bar')` even though we don't know for sure if `foo` is an Ember object (default `false`)
 
diff --git a/lib/rules/avoid-leaking-state-in-ember-objects.js b/lib/rules/avoid-leaking-state-in-ember-objects.js
index fd8ea8c7..f77b2599 100644
--- a/lib/rules/avoid-leaking-state-in-ember-objects.js
+++ b/lib/rules/avoid-leaking-state-in-ember-objects.js
@@ -69,10 +69,10 @@ module.exports = {
     ],
   },
 
-  DEFAULT_IGNORED_PROPERTIES,
-
   create(context) {
-    const ignoredProperties = context.options[0] || DEFAULT_IGNORED_PROPERTIES;
+    const ignoredProperties = context.options[0]
+      ? [...DEFAULT_IGNORED_PROPERTIES, ...context.options[0]]
+      : DEFAULT_IGNORED_PROPERTIES;
 
     const report = function (node) {
       const message =
diff --git a/lib/rules/no-duplicate-dependent-keys.js b/lib/rules/no-duplicate-dependent-keys.js
index c6bb4609..c6a78338 100644
--- a/lib/rules/no-duplicate-dependent-keys.js
+++ b/lib/rules/no-duplicate-dependent-keys.js
@@ -3,7 +3,6 @@
 const emberUtils = require('../utils/ember');
 const types = require('../utils/types');
 const fixerUtils = require('../utils/fixer');
-const javascriptUtils = require('../utils/javascript');
 const { getImportIdentifier } = require('../utils/import');
 
 const ERROR_MESSAGE = 'Dependent keys should not be repeated';
@@ -57,11 +56,11 @@ module.exports = {
                 return null;
               }
 
-              return javascriptUtils.flat(
-                duplicateNodes.map((duplicateNode) =>
+              return duplicateNodes
+                .map((duplicateNode) =>
                   fixerUtils.removeCommaSeparatedNode(duplicateNode, sourceCode, fixer)
                 )
-              );
+                .flat();
             },
           });
         }
diff --git a/lib/rules/no-get.js b/lib/rules/no-get.js
index 33b2b6b1..9882d106 100644
--- a/lib/rules/no-get.js
+++ b/lib/rules/no-get.js
@@ -107,7 +107,7 @@ module.exports = {
           },
           useOptionalChaining: {
             type: 'boolean',
-            default: false,
+            default: true,
           },
           catchSafeObjects: {
             type: 'boolean',
diff --git a/lib/rules/require-computed-property-dependencies.js b/lib/rules/require-computed-property-dependencies.js
index eea05e92..55cb28b3 100644
--- a/lib/rules/require-computed-property-dependencies.js
+++ b/lib/rules/require-computed-property-dependencies.js
@@ -3,7 +3,6 @@
 const estraverse = require('estraverse');
 const emberUtils = require('../utils/ember');
 const computedPropertyUtils = require('../utils/computed-properties');
-const javascriptUtils = require('../utils/javascript');
 const types = require('../utils/types');
 const utils = require('../utils/utils');
 const propertyGetterUtils = require('../utils/property-getter');
@@ -303,13 +302,12 @@ module.exports = {
       const computedPropertyFunctionBody =
         computedPropertyUtils.getComputedPropertyFunctionBody(node);
 
-      const usedKeys1 = javascriptUtils.flatMap(
-        findEmberGetCalls(computedPropertyFunctionBody, importedNames),
+      const usedKeys1 = findEmberGetCalls(computedPropertyFunctionBody, importedNames).flatMap(
         (node) => extractEmberGetDependencies(node, importedNames)
       );
-      const usedKeys2 = javascriptUtils.flatMap(
-        findThisGetCalls(computedPropertyFunctionBody),
-        (node) => extractThisGetDependencies(node, context)
+
+      const usedKeys2 = findThisGetCalls(computedPropertyFunctionBody).flatMap((node) =>
+        extractThisGetDependencies(node, context)
       );
       const usedKeys = [...usedKeys1, ...usedKeys2];
 
diff --git a/lib/utils/computed-property-dependent-keys.js b/lib/utils/computed-property-dependent-keys.js
index 8903d1e5..2028c5f4 100644
--- a/lib/utils/computed-property-dependent-keys.js
+++ b/lib/utils/computed-property-dependent-keys.js
@@ -64,7 +64,7 @@ function collapseKeys(keys) {
  * @returns {Array<string>}
  */
 function expandKeys(keys) {
-  return javascriptUtils.flatMap(keys, expandKey);
+  return keys.flatMap(expandKey);
 }
 
 /**
@@ -199,7 +199,7 @@ function findComputedPropertyDependentKeys(
   if (types.isClassDeclaration(nodeClass)) {
     // Native JS class.
     return new Set(
-      javascriptUtils.flatMap(nodeClass.body.body, (node) => {
+      nodeClass.body.body.flatMap((node) => {
         // Check for `computed` itself.
         const computedDecorator = decoratorUtils.findDecorator(node, computedImportName);
         if (computedDecorator) {
@@ -224,31 +224,28 @@ function findComputedPropertyDependentKeys(
   } else if (types.isCallExpression(nodeClass)) {
     // Classic class.
     return new Set(
-      javascriptUtils.flatMap(
-        nodeClass.arguments.filter(types.isObjectExpression),
-        (classObject) => {
-          return javascriptUtils.flatMap(classObject.properties, (node) => {
-            if (types.isProperty(node)) {
-              const name = getName(node.value);
-              if (!name) {
-                return [];
-              }
-
-              // Check for `computed` itself.
-              if (name === computedImportName) {
-                return getComputedPropertyDependentKeys(node.value);
-              }
-
-              // Check for a computed macro.
-              const macroConfigsByName = getMacrosFromImports(macrosByName, macrosByIndexName);
-              if (macroConfigsByName.has(name)) {
-                return getComputedPropertyDependentKeys(node.value, macroConfigsByName.get(name));
-              }
+      nodeClass.arguments.filter(types.isObjectExpression).flatMap((classObject) => {
+        return classObject.properties.flatMap((node) => {
+          if (types.isProperty(node)) {
+            const name = getName(node.value);
+            if (!name) {
+              return [];
             }
-            return [];
-          });
-        }
-      )
+
+            // Check for `computed` itself.
+            if (name === computedImportName) {
+              return getComputedPropertyDependentKeys(node.value);
+            }
+
+            // Check for a computed macro.
+            const macroConfigsByName = getMacrosFromImports(macrosByName, macrosByIndexName);
+            if (macroConfigsByName.has(name)) {
+              return getComputedPropertyDependentKeys(node.value, macroConfigsByName.get(name));
+            }
+          }
+          return [];
+        });
+      })
     );
   } else {
     assert(false, 'Unexpected node type for a class.');
@@ -324,19 +321,16 @@ function getComputedPropertyDependentKeys(node, computedDependenciesConfig = nul
     return [];
   }
 
-  const baseKeys = javascriptUtils.flatMap(node.arguments, (argument, index) => {
+  const baseKeys = node.arguments.flatMap((argument, index) => {
     if (!computedDependenciesConfig) {
       return argument.type === 'Literal' && typeof argument.value === 'string'
         ? [argument.value]
         : [];
     }
 
-    return javascriptUtils.flatMap(
-      computedDependenciesConfig.argumentFormat,
-      ({ strings, objects }) => {
-        return getComputedPropertyDependentKeysForConfig(argument, index, { strings, objects });
-      }
-    );
+    return computedDependenciesConfig.argumentFormat.flatMap(({ strings, objects }) => {
+      return getComputedPropertyDependentKeysForConfig(argument, index, { strings, objects });
+    });
   });
 
   return expandKeys(baseKeys);
diff --git a/lib/utils/computed-property-macros.js b/lib/utils/computed-property-macros.js
index 48acbee2..a2fc688d 100644
--- a/lib/utils/computed-property-macros.js
+++ b/lib/utils/computed-property-macros.js
@@ -1,5 +1,4 @@
 const assert = require('assert');
-const { flatMap } = require('./javascript');
 
 /**
  * Example macros:
@@ -77,7 +76,7 @@ function getMacros() {
 function getMacrosFromImports(macrosByImport, macrosByIndexImport) {
   return new Map([
     ...macrosByImport,
-    ...flatMap([...macrosByIndexImport], ([indexName, propertyConfigs]) => {
+    ...[...macrosByIndexImport].flatMap(([indexName, propertyConfigs]) => {
       return [...propertyConfigs].map(([name, config]) => [`${indexName}.${name}`, config]);
     }),
   ]);
diff --git a/lib/utils/ember.js b/lib/utils/ember.js
index ccd2e86c..00dcc50c 100644
--- a/lib/utils/ember.js
+++ b/lib/utils/ember.js
@@ -7,7 +7,6 @@ const types = require('./types');
 const assert = require('assert');
 const decoratorUtils = require('../utils/decorators');
 const { getNodeOrNodeFromVariable } = require('../utils/utils');
-const { flatMap } = require('../utils/javascript');
 const kebabCase = require('lodash.kebabcase');
 
 module.exports = {
@@ -604,7 +603,7 @@ function isControllerDefaultProp(property) {
 }
 
 function getModuleProperties(moduleNode, scopeManager) {
-  return flatMap(moduleNode.arguments, (arg) => {
+  return moduleNode.arguments.flatMap((arg) => {
     const resultingNode = getNodeOrNodeFromVariable(arg, scopeManager);
     return resultingNode && resultingNode.type === 'ObjectExpression'
       ? resultingNode.properties
diff --git a/lib/utils/javascript.js b/lib/utils/javascript.js
index 1d44fd7e..8ba4a834 100644
--- a/lib/utils/javascript.js
+++ b/lib/utils/javascript.js
@@ -2,8 +2,6 @@
 
 module.exports = {
   duplicateArrays,
-  flat,
-  flatMap,
   removeWhitespace,
 };
 
@@ -21,37 +19,6 @@ function duplicateArrays(arr, times) {
   return result;
 }
 
-/**
- * Flattens an array.
- * WARNING: can only handle one level of nested arrays.
- * TODO: switch to native JavaScript `flat` once Node 12 is the minimum version we need to support.
- * Input: [[1], [2]]
- * Output: [1, 2]
- *
- * @param  {Object[]} arr Array to flatten.
- * @return {Object[]}     Flattened array.
- */
-function flat(arr) {
-  if (!arr || arr.length === 0) {
-    return arr;
-  }
-
-  return arr.reduce((acc, val) => acc.concat(val)); // eslint-disable-line unicorn/prefer-spread
-}
-
-/**
- * Builds an array by concatenating the results of a map.
- * TODO: switch to native JavaScript `flatMap` once Node 12 is the minimum version we need to support.
- *
- * @template T, U
- * @param {Array<T>} array
- * @param {function(T): Array<U>} callback
- * @returns {Array<U>}
- */
-function flatMap(array, callback) {
-  return array.reduce((result, item, index) => result.concat(callback(item, index)), []); // eslint-disable-line unicorn/prefer-spread
-}
-
 function removeWhitespace(str) {
   // Removes whitespace anywhere inside string.
   return str.replace(/\s/g, '');
diff --git a/package.json b/package.json
index d512fb12..024f5986 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,8 @@
     "url": "git+https://github.com/ember-cli/eslint-plugin-ember.git"
   },
   "license": "MIT",
-  "main": "lib/index.js",
+  "exports": "./lib/index.js",
+  "main": "./lib/index.js",
   "directories": {
     "test": "test",
     "rules": "rules"
@@ -100,7 +101,7 @@
     "eslint": ">= 6"
   },
   "engines": {
-    "node": "10.* || 12.* || >= 14"
+    "node": "14.* || 16.* || >= 18"
   },
   "publishConfig": {
     "registry": "https://registry.npmjs.org"


Test patch:
diff --git a/tests/lib/rules/avoid-leaking-state-in-ember-objects.js b/tests/lib/rules/avoid-leaking-state-in-ember-objects.js
index 1e9b10ce..b093ddb3 100644
--- a/tests/lib/rules/avoid-leaking-state-in-ember-objects.js
+++ b/tests/lib/rules/avoid-leaking-state-in-ember-objects.js
@@ -9,22 +9,6 @@ const RuleTester = require('eslint').RuleTester;
 // Tests
 // ------------------------------------------------------------------------------
 
-describe('imports', () => {
-  it('should expose the default ignored properties', () => {
-    expect(rule.DEFAULT_IGNORED_PROPERTIES).toStrictEqual([
-      'classNames',
-      'classNameBindings',
-      'actions',
-      'concatenatedProperties',
-      'mergedProperties',
-      'positionalParams',
-      'attributeBindings',
-      'queryParams',
-      'attrs',
-    ]);
-  });
-});
-
 const eslintTester = new RuleTester({
   parser: require.resolve('@babel/eslint-parser'),
   parserOptions: { ecmaVersion: 2020, sourceType: 'module' },
@@ -71,6 +55,8 @@ eslintTester.run('avoid-leaking-state-in-ember-objects', rule, {
     'import Component from "@ember/component"; export default class MyNativeClassComponent extends Component { someArrayField = []; }',
     'import Component from "@ember/component"; export default class MyNativeClassComponent extends Component { someObjectField = {}; }',
     'import EmberObject from "@ember/object"; export default class MyNativeClassComponentWithAMixin extends EmberObject.extend(MyMixin) { someArrayField = []; }',
+    { code: 'export default Foo.extend({ someProp: [] });', options: [['someProp']] }, // With options.
+    { code: 'export default Foo.extend({ someProp: [], actions: {} });', options: [['someProp']] }, // With options and known Ember property.
   ],
   invalid: [
     {
@@ -83,6 +69,18 @@ eslintTester.run('avoid-leaking-state-in-ember-objects', rule, {
         },
       ],
     },
+    {
+      // With options.
+      code: 'export default Foo.extend({someProp: [], someProp2: [], actions: {} });',
+      output: null,
+      options: [['someProp']],
+      errors: [
+        {
+          message:
+            'Only string, number, symbol, boolean, null, undefined, and function are allowed as default properties',
+        },
+      ],
+    },
     {
       // With object variable.
       code: 'const body = {someProp: []}; export default Foo.extend(body);',
diff --git a/tests/lib/rules/no-get.js b/tests/lib/rules/no-get.js
index cb8b39fb..84d2bea3 100644
--- a/tests/lib/rules/no-get.js
+++ b/tests/lib/rules/no-get.js
@@ -19,10 +19,13 @@ ruleTester.run('no-get', rule, {
     // **************************
 
     // Nested property path.
-    { code: "this.get('foo.bar');", options: [{ ignoreNestedPaths: true }] },
+    {
+      code: "this.get('foo.bar');",
+      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],
+    },
     {
       code: "import { get } from '@ember/object'; get(this, 'foo.bar');",
-      options: [{ ignoreNestedPaths: true }],
+      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],
     },
 
     // Template literals.
@@ -74,15 +77,21 @@ ruleTester.run('no-get', rule, {
     // **************************
 
     // Nested property path.
-    { code: "this.getProperties('foo', 'bar.baz');", options: [{ ignoreNestedPaths: true }] },
-    { code: "this.getProperties(['foo', 'bar.baz']);", options: [{ ignoreNestedPaths: true }] }, // With parameters in array.
+    {
+      code: "this.getProperties('foo', 'bar.baz');",
+      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],
+    },
+    {
+      code: "this.getProperties(['foo', 'bar.baz']);",
+      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],
+    }, // With parameters in array.
     {
       code: "import { getProperties } from '@ember/object'; getProperties(this, 'foo', 'bar.baz');",
-      options: [{ ignoreNestedPaths: true }],
+      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],
     },
     {
       code: "import { getProperties } from '@ember/object'; getProperties(this, ['foo', 'bar.baz']);",
-      options: [{ ignoreNestedPaths: true }],
+      options: [{ ignoreNestedPaths: true, useOptionalChaining: false }],
     }, // With parameters in array.
 
     // Template literals.
@@ -216,12 +225,21 @@ ruleTester.run('no-get', rule, {
       errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],
     },
     {
+      // useOptionalChaining = false
       code: "foo1.foo2.get('bar.bar').baz;",
       output: null,
+      options: [{ catchUnsafeObjects: true, useOptionalChaining: false }],
+      errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],
+    },
+    {
+      // useOptionalChaining = true (implicit)
+      code: "foo1.foo2.get('bar.bar').baz;",
+      output: 'foo1.foo2.bar.bar.baz;',
       options: [{ catchUnsafeObjects: true }],
       errors: [{ message: ERROR_MESSAGE_GET, type: 'CallExpression' }],
     },
     {
+      // useOptionalChaining = true (explicit)
       code: "foo1.foo2.get('bar.bar').baz;",
       output: 'foo1.foo2.bar.bar.baz;',
       options: [{ catchUnsafeObjects: true, useOptionalChaining: true }],
diff --git a/tests/lib/rules/no-invalid-debug-function-arguments.js b/tests/lib/rules/no-invalid-debug-function-arguments.js
index d6ae3669..26a31cbe 100644
--- a/tests/lib/rules/no-invalid-debug-function-arguments.js
+++ b/tests/lib/rules/no-invalid-debug-function-arguments.js
@@ -4,7 +4,6 @@
 
 const rule = require('../../../lib/rules/no-invalid-debug-function-arguments');
 const RuleTester = require('eslint').RuleTester;
-const javascriptUtils = require('../../../lib/utils/javascript');
 
 const { DEBUG_FUNCTIONS, ERROR_MESSAGE } = rule;
 
@@ -21,102 +20,98 @@ const VALID_USAGES_BASIC = [
   },
 ];
 
-const VALID_USAGES_FOR_EACH_DEBUG_FUNCTION = javascriptUtils.flat(
-  DEBUG_FUNCTIONS.map((debugFunction) => [
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; OtherClass.${debugFunction}(true, 'My string.');`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}();`,
-    },
-    {
-      code: `Ember.${debugFunction}();`,
-    },
-    {
-      // Missing import:
-      code: `${debugFunction}(true, 'My string.');`,
-    },
-    {
-      // Wrong import:
-      code: `import { ${debugFunction} } from 'something-else'; ${debugFunction}(true, 'My string.');`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.');`,
-    },
-    {
-      code: `Ember.${debugFunction}('My description.');`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', true);`,
-    },
-    {
-      code: `Ember.${debugFunction}('My description.', true);`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', true, { id: 'some-id' });`,
-    },
-    {
-      code: `Ember.${debugFunction}('My description.', true, { id: 'some-id' });`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', CONDITION);`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(DESCRIPTION, true);`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(DESCRIPTION, CONDITION);`,
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}.unrelatedFunction(true, 'My description.');`,
-    },
-    {
-      code: `Ember.${debugFunction}.unrelatedFunction(true, 'My description.');`,
-    },
-  ])
-);
+const VALID_USAGES_FOR_EACH_DEBUG_FUNCTION = DEBUG_FUNCTIONS.map((debugFunction) => [
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; OtherClass.${debugFunction}(true, 'My string.');`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}();`,
+  },
+  {
+    code: `Ember.${debugFunction}();`,
+  },
+  {
+    // Missing import:
+    code: `${debugFunction}(true, 'My string.');`,
+  },
+  {
+    // Wrong import:
+    code: `import { ${debugFunction} } from 'something-else'; ${debugFunction}(true, 'My string.');`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.');`,
+  },
+  {
+    code: `Ember.${debugFunction}('My description.');`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', true);`,
+  },
+  {
+    code: `Ember.${debugFunction}('My description.', true);`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', true, { id: 'some-id' });`,
+  },
+  {
+    code: `Ember.${debugFunction}('My description.', true, { id: 'some-id' });`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}('My description.', CONDITION);`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(DESCRIPTION, true);`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(DESCRIPTION, CONDITION);`,
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}.unrelatedFunction(true, 'My description.');`,
+  },
+  {
+    code: `Ember.${debugFunction}.unrelatedFunction(true, 'My description.');`,
+  },
+]).flat();
 
 const VALID_USAGES = [...VALID_USAGES_BASIC, ...VALID_USAGES_FOR_EACH_DEBUG_FUNCTION];
 
-const INVALID_USAGES = javascriptUtils.flat(
-  DEBUG_FUNCTIONS.map((debugFunction) => [
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, 'My description.');`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-    {
-      code: `Ember.${debugFunction}(true, 'My description.');`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, 'My description.', { id: 'some-id' });`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-    {
-      code: `Ember.${debugFunction}(true, 'My description.', { id: 'some-id' });`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, \`My \${123} description.\`);`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(CONDITION, 'My description.');`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-    {
-      code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(CONDITION, \`My \${123} description.\`);`,
-      output: null,
-      errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
-    },
-  ])
-);
+const INVALID_USAGES = DEBUG_FUNCTIONS.map((debugFunction) => [
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, 'My description.');`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+  {
+    code: `Ember.${debugFunction}(true, 'My description.');`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, 'My description.', { id: 'some-id' });`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+  {
+    code: `Ember.${debugFunction}(true, 'My description.', { id: 'some-id' });`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(true, \`My \${123} description.\`);`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(CONDITION, 'My description.');`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+  {
+    code: `import { ${debugFunction} } from '@ember/debug'; ${debugFunction}(CONDITION, \`My \${123} description.\`);`,
+    output: null,
+    errors: [{ message: ERROR_MESSAGE, type: 'CallExpression' }],
+  },
+]).flat();
 
 const ruleTester = new RuleTester({
   parserOptions: {
diff --git a/tests/lib/utils/javascript-test.js b/tests/lib/utils/javascript-test.js
index 758f6f7f..421184b1 100644
--- a/tests/lib/utils/javascript-test.js
+++ b/tests/lib/utils/javascript-test.js
@@ -17,23 +17,6 @@ describe('duplicateArrays', () => {
   });
 });
 
-describe('flat', function () {
-  it('correctly flattens arrays', function () {
-    expect(javascriptUtils.flat([])).toStrictEqual([]);
-    expect(javascriptUtils.flat(null)).toBeNull();
-    expect(javascriptUtils.flat([[1], [2]])).toStrictEqual([1, 2]);
-    expect(
-      javascriptUtils.flat([
-        [1, 2],
-        [3, 4],
-      ])
-    ).toStrictEqual([1, 2, 3, 4]);
-    expect(javascriptUtils.flat([[1], [2, 3], [4, 5, 6]])).toStrictEqual([1, 2, 3, 4, 5, 6]); // different lengths forward
-    expect(javascriptUtils.flat([[6, 5, 4], [3, 2], [1]])).toStrictEqual([6, 5, 4, 3, 2, 1]); // different lengths reverse
-    expect(javascriptUtils.flat([[1], null, [2]])).toStrictEqual([1, null, 2]); // with null array
-  });
-});
-
 describe('removeWhitespace', () => {
   it('returns the right result', () => {
     expect(javascriptUtils.removeWhitespace('abcdef')).toStrictEqual('abcdef');
diff --git a/tests/rule-setup.js b/tests/rule-setup.js
index 53885073..f6426cd7 100644
--- a/tests/rule-setup.js
+++ b/tests/rule-setup.js
@@ -4,7 +4,6 @@ const { readdirSync, readFileSync } = require('fs');
 const path = require('path');
 const rules = require('../lib').rules;
 const recommendedRules = require('../lib/recommended-rules');
-const { flat } = require('../lib/utils/javascript');
 
 const RULE_NAMES = Object.keys(rules);
 const RECOMMENDED_RULE_NAMES = Object.keys(recommendedRules).map((name) =>
@@ -17,7 +16,7 @@ function getAllNamedOptions(jsonSchema) {
   }
 
   if (Array.isArray(jsonSchema)) {
-    return flat(jsonSchema.map(getAllNamedOptions));
+    return jsonSchema.map(getAllNamedOptions).flat();
   }
 
   if (jsonSchema.items) {