Repository: ember-cli/eslint-plugin-ember
Pull request #781 (2020-04-14 17:45:08)

Add decorator support to `no-unnecessary-service-injection-argument` rule
Handles decorator usage and autofixes.

Before:

```js
class Test { @service("serviceName") serviceName }
```

After:

```js
class Test { @service() serviceName }
```

Helps with #560 #566.


Patch:
diff --git a/docs/rules/no-unnecessary-service-injection-argument.md b/docs/rules/no-unnecessary-service-injection-argument.md
index f9202425..959c1870 100644
--- a/docs/rules/no-unnecessary-service-injection-argument.md
+++ b/docs/rules/no-unnecessary-service-injection-argument.md
@@ -43,9 +43,3 @@ export default Component.extend({
 
 * Ember [Services](https://guides.emberjs.com/release/applications/services/) guide
 * Ember [inject](https://emberjs.com/api/ember/release/functions/@ember%2Fservice/inject) function spec
-
-## Help Wanted
-
-| Issue | Link |
-| :-- | :-- |
-| :x: Missing native JavaScript class support | [#560](https://github.com/ember-cli/eslint-plugin-ember/issues/560) |
diff --git a/lib/rules/no-unnecessary-service-injection-argument.js b/lib/rules/no-unnecessary-service-injection-argument.js
index fa9b0842..7d6e5f3b 100644
--- a/lib/rules/no-unnecessary-service-injection-argument.js
+++ b/lib/rules/no-unnecessary-service-injection-argument.js
@@ -32,19 +32,45 @@ module.exports = {
         if (
           !emberUtils.isInjectedServiceProp(node) ||
           node.value.arguments.length !== 1 ||
-          !types.isLiteral(node.value.arguments[0])
+          !types.isStringLiteral(node.value.arguments[0])
         ) {
           return;
         }
 
         const keyName = node.key.name;
-        const firstArgValue = node.value.arguments[0].value;
+        const firstArg = node.value.arguments[0];
+        const firstArgValue = firstArg.value;
         if (keyName === firstArgValue) {
           context.report({
-            node: node.value.arguments[0],
+            node: firstArg,
             message: ERROR_MESSAGE,
             fix(fixer) {
-              return fixer.remove(node.value.arguments[0]);
+              return fixer.remove(firstArg);
+            },
+          });
+        }
+      },
+
+      ClassProperty(node) {
+        if (
+          !emberUtils.isInjectedServiceProp(node) ||
+          node.decorators.length !== 1 ||
+          !types.isCallExpression(node.decorators[0].expression) ||
+          node.decorators[0].expression.arguments.length !== 1 ||
+          !types.isStringLiteral(node.decorators[0].expression.arguments[0])
+        ) {
+          return;
+        }
+
+        const keyName = node.key.name;
+        const firstArg = node.decorators[0].expression.arguments[0];
+        const firstArgValue = firstArg.value;
+        if (keyName === firstArgValue) {
+          context.report({
+            node: firstArg,
+            message: ERROR_MESSAGE,
+            fix(fixer) {
+              return fixer.remove(firstArg);
             },
           });
         }


Test patch:
diff --git a/tests/lib/rules/no-unnecessary-service-injection-argument.js b/tests/lib/rules/no-unnecessary-service-injection-argument.js
index 1cb91ad1..a94c93f7 100644
--- a/tests/lib/rules/no-unnecessary-service-injection-argument.js
+++ b/tests/lib/rules/no-unnecessary-service-injection-argument.js
@@ -24,31 +24,74 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
     'export default Component.extend({ serviceName: service() });',
     'export default Component.extend({ serviceName: inject() });',
     'const controller = Controller.extend({ serviceName: service() });',
+    {
+      code: 'class Test { @service serviceName }',
+      parser: require.resolve('babel-eslint'),
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        ecmaFeatures: { legacyDecorators: true },
+      },
+    },
+    {
+      code: 'class Test { @service() serviceName }',
+      parser: require.resolve('babel-eslint'),
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        ecmaFeatures: { legacyDecorators: true },
+      },
+    },
 
     // Property name matches service name but service name uses dashes
     // (allowed because it avoids needless runtime camelization <-> dasherization in the resolver):
     "export default Component.extend({ specialName: service('service-name') });",
     "export default Component.extend({ specialName: inject('service-name') });",
     "const controller = Controller.extend({ serviceName: service('service-name') });",
+    {
+      code: 'class Test { @service("service-name") serviceName }',
+      parser: require.resolve('babel-eslint'),
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        ecmaFeatures: { legacyDecorators: true },
+      },
+    },
 
     // Property name does not match service name:
     "const controller = Controller.extend({ specialName: service('service-name') });",
+    {
+      code: 'class Test { @service("specialName") serviceName }',
+      parser: require.resolve('babel-eslint'),
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        ecmaFeatures: { legacyDecorators: true },
+      },
+    },
 
     // When usage is ignored because of additional arguments:
     "export default Component.extend({ serviceName: service('serviceName', EXTRA_PROPERTY) });",
     "export default Component.extend({ serviceName: inject('serviceName', EXTRA_PROPERTY) });",
 
+    // When usage is ignored because of template literal:
+    'export default Component.extend({ serviceName: service(`serviceName`) });',
+    {
+      code: 'class Test { @service(`specialName`) serviceName }',
+      parser: require.resolve('babel-eslint'),
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        ecmaFeatures: { legacyDecorators: true },
+      },
+    },
+
     // Not Ember's `service()` function:
     "export default Component.extend({ serviceName: otherFunction('serviceName') });",
     "export default Component.extend({ serviceName: service.otherFunction('serviceName') });",
     "export default Component.extend({ serviceName: inject.otherFunction('serviceName') });",
-
-    // Decorator:
     {
-      // TODO: this should be an invalid test case.
-      // Still missing native class and decorator support: https://github.com/ember-cli/eslint-plugin-ember/issues/560
-      code: "class Test { @service('serviceName') serviceName }",
-      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],
+      code: 'class Test { @otherDecorator("name") name }',
       parser: require.resolve('babel-eslint'),
       parserOptions: {
         ecmaVersion: 6,
@@ -81,5 +124,18 @@ ruleTester.run('no-unnecessary-service-injection-argument', rule, {
       output: 'const controller = Controller.extend({ serviceName: inject() });',
       errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],
     },
+
+    // Decorator:
+    {
+      code: 'class Test { @service("serviceName") serviceName }',
+      output: 'class Test { @service() serviceName }',
+      errors: [{ message: ERROR_MESSAGE, type: 'Literal' }],
+      parser: require.resolve('babel-eslint'),
+      parserOptions: {
+        ecmaVersion: 6,
+        sourceType: 'module',
+        ecmaFeatures: { legacyDecorators: true },
+      },
+    },
   ],
 });