Repository: ember-cli/eslint-plugin-ember
Pull request #768 (2020-04-12 16:21:38)

Catch leading or trailing periods in `no-invalid-dependent-keys`
Another real-world common mistake/typo I've seen.

Example:

```js
export default Component.extend({
  // Leading period:
  userId: computed('.user.id', {
    // Code
  })
});
```


Patch:
diff --git a/docs/rules/no-invalid-dependent-keys.md b/docs/rules/no-invalid-dependent-keys.md
index b3711821..1a4daaca 100644
--- a/docs/rules/no-invalid-dependent-keys.md
+++ b/docs/rules/no-invalid-dependent-keys.md
@@ -10,15 +10,16 @@ Dependent keys used for computed properties have to be valid.
 
 This rule aims to avoid invalid dependent keys in computed properties.
 
-Currently implemented:
+Currently implemented checks:
 
 - Unbalanced open and closed braces. These can be hard to track for complex computed properties and are usually unchecked since the expressions are passed as Strings.
 - Unnecessary braces
 - Invalid position of `@each` or `[]`
+- Leading or trailing periods
 
-Not yet implemented:
+Not yet implemented checks:
 
-- Check for invalid characters
+- Invalid characters
 
 ## Examples
 
@@ -60,6 +61,15 @@ export default Component.extend({
 });
 ```
 
+```js
+export default Component.extend({
+  // Leading period:
+  userId: computed('.user.id', {
+    // Code
+  })
+});
+```
+
 Examples of **correct** code for this rule:
 
 ```js
diff --git a/lib/rules/no-invalid-dependent-keys.js b/lib/rules/no-invalid-dependent-keys.js
index 0fac2a26..a5cc2f82 100644
--- a/lib/rules/no-invalid-dependent-keys.js
+++ b/lib/rules/no-invalid-dependent-keys.js
@@ -11,6 +11,7 @@ const ERROR_MESSAGE_UNBALANCED_BRACES = 'Found unbalanced braces in dependent ke
 const ERROR_MESSAGE_UNNECESSARY_BRACES = 'Found unnecessary braces in dependent key';
 const ERROR_MESSAGE_TERMINAL_AT_EACH = 'Found terminal `@each`, use `[]` instead';
 const ERROR_MESSAGE_MIDDLE_BRACKETS = '`[]` should only be used at the end of the dependent key';
+const ERROR_MESSAGE_LEADING_TRAILING_PERIOD = 'Found leading or trailing period in dependent key';
 
 function hasUnbalancedBraces(str) {
   const foundBraces = str.match(/[{}]/g) || [];
@@ -50,6 +51,7 @@ module.exports = {
   ERROR_MESSAGE_UNNECESSARY_BRACES,
   ERROR_MESSAGE_TERMINAL_AT_EACH,
   ERROR_MESSAGE_MIDDLE_BRACKETS,
+  ERROR_MESSAGE_LEADING_TRAILING_PERIOD,
 
   create(context) {
     return {
@@ -104,8 +106,35 @@ module.exports = {
               },
             });
           }
+
+          if (node.value.startsWith('.')) {
+            context.report({
+              node,
+              message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,
+              fix(fixer) {
+                const nodeText = sourceCode.getText(node);
+                return fixer.replaceText(node, nodeText.replace('.', '')); // Remove first period.
+              },
+            });
+          }
+
+          if (node.value.endsWith('.')) {
+            context.report({
+              node,
+              message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,
+              fix(fixer) {
+                const nodeText = sourceCode.getText(node);
+                return fixer.replaceText(node, removeLastOccurrenceOf(nodeText, '.'));
+              },
+            });
+          }
         });
       },
     };
   },
 };
+
+function removeLastOccurrenceOf(strToSearch, strToRemove) {
+  const posToRemove = strToSearch.lastIndexOf(strToRemove);
+  return strToSearch.slice(0, posToRemove) + strToSearch.slice(posToRemove + 1);
+}


Test patch:
diff --git a/tests/lib/rules/no-invalid-dependent-keys.js b/tests/lib/rules/no-invalid-dependent-keys.js
index 2671d0b9..6cb2adca 100644
--- a/tests/lib/rules/no-invalid-dependent-keys.js
+++ b/tests/lib/rules/no-invalid-dependent-keys.js
@@ -6,6 +6,7 @@ const {
   ERROR_MESSAGE_UNNECESSARY_BRACES,
   ERROR_MESSAGE_TERMINAL_AT_EACH,
   ERROR_MESSAGE_MIDDLE_BRACKETS,
+  ERROR_MESSAGE_LEADING_TRAILING_PERIOD,
 } = rule;
 
 // ------------------------------------------------------------------------------
@@ -167,6 +168,28 @@ eslintTester.run('no-invalid-dependent-keys', rule, {
       ],
     },
 
+    // Extra periods
+    {
+      code: 'computed(".foo.bar", function() {})',
+      output: 'computed("foo.bar", function() {})',
+      errors: [
+        {
+          message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,
+          type: 'Literal',
+        },
+      ],
+    },
+    {
+      code: 'computed("foo.bar.", function() {})',
+      output: 'computed("foo.bar", function() {})',
+      errors: [
+        {
+          message: ERROR_MESSAGE_LEADING_TRAILING_PERIOD,
+          type: 'Literal',
+        },
+      ],
+    },
+
     // Decorator
     {
       code: "class Test { @computed('foo.{name,place') get someProp() { return true; } }",