Repository: ember-cli/eslint-plugin-ember
Pull request #1870 (2023-05-21 16:30:53)

Fix crash with default computed property import in `no-unused-services` rule
Crash detected by:

* https://github.com/ember-cli/eslint-plugin-ember/pull/1741

```
Rule: "ember/no-unused-services"
Path: emberjs/data/@types/@ember/array/-private/enumerable.d.ts
Link: https://github.com/emberjs/data/blob/HEAD/@types/@ember/array/-private/enumerable.d.ts#L2

  1 | import type NativeArray from '@ember/array/-private/native-array';
> 2 | import type ComputedProperty from '@ember/object/computed';
  3 | import type Mixin from '@ember/object/mixin';
  4 | /**
  5 |  * This mixin defines the common interface implemented by enumerable objects

Error:
TypeError: Cannot read property 'name' of undefined
Occurred while linting /home/runner/work/eslint-plugin-ember/eslint-plugin-ember/node_modules/eslint-remote-tester/.cache-eslint-remote-tester/emberjs/data/@types/@ember/array/-private/enumerable.d.ts:2
Rule: "ember/no-unused-services"
    at ImportDeclaration (/home/runner/work/eslint-plugin-ember/eslint-plugin-ember/lib/rules/no-unused-services.js:133:40)
```


Patch:
diff --git a/lib/rules/no-restricted-service-injections.js b/lib/rules/no-restricted-service-injections.js
index 9504e663..e9d83b77 100644
--- a/lib/rules/no-restricted-service-injections.js
+++ b/lib/rules/no-restricted-service-injections.js
@@ -101,7 +101,9 @@ module.exports = {
       }
 
       // Find the service decorator.
-      const serviceDecorator = decoratorUtils.findDecorator(node, 'service');
+      const serviceDecorator =
+        decoratorUtils.findDecorator(node, 'service') ||
+        decoratorUtils.findDecorator(node, 'inject');
 
       // Get the service name either from the string argument or from the property name.
       const serviceName =
diff --git a/lib/rules/no-unused-services.js b/lib/rules/no-unused-services.js
index 3d577a1c..aa7323f2 100644
--- a/lib/rules/no-unused-services.js
+++ b/lib/rules/no-unused-services.js
@@ -130,6 +130,9 @@ module.exports = {
         }
         if (node.source.value === '@ember/object/computed') {
           for (const spec of node.specifiers) {
+            if (spec.type === 'ImportDefaultSpecifier') {
+              continue;
+            }
             const name = spec.imported.name;
             if (macros.includes(name)) {
               const localName = spec.local.name;


Test patch:
diff --git a/tests/lib/rules/no-restricted-service-injections.js b/tests/lib/rules/no-restricted-service-injections.js
index d3eea967..226843ca 100644
--- a/tests/lib/rules/no-restricted-service-injections.js
+++ b/tests/lib/rules/no-restricted-service-injections.js
@@ -4,6 +4,7 @@ const rule = require('../../../lib/rules/no-restricted-service-injections');
 const { DEFAULT_ERROR_MESSAGE } = rule;
 
 const EMBER_IMPORT = "import Ember from 'ember';";
+const INJECT_IMPORT = "import {inject} from '@ember/service';";
 const SERVICE_IMPORT = "import {inject as service} from '@ember/service';";
 const NEW_SERVICE_IMPORT = "import {service} from '@ember/service';";
 
@@ -23,6 +24,12 @@ ruleTester.run('no-restricted-service-injections', rule, {
       code: `${SERVICE_IMPORT} Component.extend({ myService: service() })`,
       options: [{ paths: ['app/components'], services: ['abc'] }],
     },
+    {
+      // Service name doesn't match (with property name):
+      filename: 'app/components/path.js',
+      code: `${INJECT_IMPORT} Component.extend({ myService: inject() })`,
+      options: [{ paths: ['app/components'], services: ['abc'] }],
+    },
     {
       // Service name doesn't match (with property name):
       filename: 'app/components/path.js',
@@ -148,6 +155,19 @@ ruleTester.run('no-restricted-service-injections', rule, {
         },
       ],
     },
+    {
+      // With decorator with camelized service name argument:
+      filename: 'app/components/path.js',
+      code: `${INJECT_IMPORT} class MyComponent extends Component { @inject('myService') randomName }`,
+      output: null,
+      options: [{ paths: ['app/components'], services: ['my-service'] }],
+      errors: [
+        {
+          message: DEFAULT_ERROR_MESSAGE,
+          // type could be ClassProperty (ESLint v7) or PropertyDefinition (ESLint v8)
+        },
+      ],
+    },
     {
       // With decorator with dasherized service name argument:
       filename: 'app/components/path.js',
@@ -236,5 +256,21 @@ ruleTester.run('no-restricted-service-injections', rule, {
       options: [{ services: ['my-service'] }],
       errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'Property' }],
     },
+
+    {
+      // TypeScript annotated injection (TypeScript).
+      code: `
+      import Route from '@ember/routing/route';
+      ${INJECT_IMPORT}
+      import type Store from '@ember-data/store';
+      export default class ApplicationRoute extends Route {
+        @inject declare store: Store;
+      }
+      `,
+      output: null,
+      options: [{ services: ['store'] }],
+      parser: require.resolve('@typescript-eslint/parser'),
+      errors: [{ message: DEFAULT_ERROR_MESSAGE, type: 'PropertyDefinition' }],
+    },
   ],
 });
diff --git a/tests/lib/rules/no-unused-services.js b/tests/lib/rules/no-unused-services.js
index 3335f138..2f839863 100644
--- a/tests/lib/rules/no-unused-services.js
+++ b/tests/lib/rules/no-unused-services.js
@@ -187,6 +187,7 @@ ruleTester.run('no-unused-services', rule, {
     `${SERVICE_IMPORT} class MyClass {}`,
     `${SERVICE_IMPORT} Component.extend({});`,
     'const foo = service();',
+    "import ComputedProperty from '@ember/object/computed';",
   ],
   invalid: [
     {