Repository: ember-template-lint/ember-template-lint
Pull request #535 (2018-10-08 18:52:03)

Add no-action-modifiers rule


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index df028dbf..5181c8b6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
 Changelog
 =========
 
+## Unreleased
+
+- Add `no-action-modifiers` rule. See the [rule documentation](https://github.com/ember-template-lint/ember-template-lint/blob/master/docs/rule/no-action-modifiers.md) for details.
+
 ## v1.0.0-beta.6
 
 - Fix a number of issues with attribute-indentation:
diff --git a/docs/rule/no-action-modifiers.md b/docs/rule/no-action-modifiers.md
new file mode 100644
index 00000000..d2abf718
--- /dev/null
+++ b/docs/rule/no-action-modifiers.md
@@ -0,0 +1,19 @@
+## no-action-modifiers
+
+This rule forbids the use of `{{action}}` modifiers on elements. The following
+code will throw an error:
+
+```hbs
+<button {{action 'handleClick'}}>
+```
+
+The following code will be accepted:
+
+```hbs
+<button onclick={{action 'handleClick'}}>
+```
+
+The following values are valid configuration:
+
+  * boolean -- `true` for enabled / `false` for disabled
+  * array -- an array of whitelisted element tag names, which will accept action modifiers
diff --git a/docs/rules.md b/docs/rules.md
index bec26846..b3a083e7 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -9,6 +9,7 @@
 * [linebreak-style](rule/linebreak-style.md)
 * [link-href-attributes](rule/link-href-attributes.md)
 * [link-rel-noopener](rule/link-rel-noopener.md)
+* [no-action-modifiers](rule/no-action-modifiers.md)
 * [no-attrs-in-components](rule/no-attrs-in-components.md)
 * [no-bare-strings](rule/no-bare-strings.md)
 * [no-debugger](rule/no-debugger.md)
diff --git a/lib/rules/index.js b/lib/rules/index.js
index bc19d758..2a8dbe49 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -13,6 +13,7 @@ module.exports = {
   'linebreak-style': require('./lint-linebreak-style'),
   'link-href-attributes': require('./lint-link-href-attributes'),
   'link-rel-noopener': require('./lint-link-rel-noopener'),
+  'no-action-modifiers': require('./lint-no-action-modifiers'),
   'no-attrs-in-components': require('./lint-no-attrs-in-components'),
   'no-bare-strings': require('./lint-no-bare-strings'),
   'no-debugger': require('./lint-no-debugger'),
diff --git a/lib/rules/lint-no-action-modifiers.js b/lib/rules/lint-no-action-modifiers.js
new file mode 100644
index 00000000..06e3eda4
--- /dev/null
+++ b/lib/rules/lint-no-action-modifiers.js
@@ -0,0 +1,67 @@
+'use strict';
+
+const Rule = require('./base');
+const createErrorMessage = require('../helpers/create-error-message');
+
+module.exports = class NoActionModifiers extends Rule {
+  parseConfig(config) {
+    switch (typeof config) {
+    case 'boolean':
+      if (config) {
+        return { whitelist: [] };
+      } else {
+        return false;
+      }
+    case 'object':
+      if (Array.isArray(config)) {
+        return { whitelist: config };
+      }
+      break;
+    case 'undefined':
+      return false;
+    }
+
+    let errorMessage = createErrorMessage(this.ruleName, [
+      '  * array of strings - tag names of elements that can accept {{action}} modifiers'
+    ], config);
+
+    throw new Error(errorMessage);
+  }
+
+  visitor() {
+    this._element = null;
+
+    return {
+      ElementNode: {
+        enter(node) {
+          this._element = node;
+        },
+
+        exit(node) {
+          if (this._element === node) {
+            this._element = null;
+          }
+        }
+      },
+      ElementModifierStatement(node) {
+        if (!this._element) { return; }
+
+        let modifierName = node.path.original;
+        if (modifierName !== 'action') {
+          return;
+        }
+
+        if (this.config.whitelist.includes(this._element.tag)) {
+          return;
+        }
+
+        this.log({
+          message: '`action` modifiers should not be used (use `onclick` attributes instead).',
+          line: node.loc && node.loc.start.line,
+          column: node.loc && node.loc.start.column,
+          source: this.sourceForNode(this._element)
+        });
+      },
+    };
+  }
+};
\ No newline at end of file


Test patch:
diff --git a/test/unit/rules/lint-no-action-modifiers-test.js b/test/unit/rules/lint-no-action-modifiers-test.js
new file mode 100644
index 00000000..1998ecd2
--- /dev/null
+++ b/test/unit/rules/lint-no-action-modifiers-test.js
@@ -0,0 +1,59 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'no-action-modifiers',
+
+  config: true,
+
+  good: [
+    '<button onclick={{action "foo"}}></button>',
+    '<a href="#" onclick={{action "foo"}}></a>',
+    '<div action></div>',
+    '{{foo-bar (action "foo")}}',
+    '{{foo-bar action}}',
+
+    {
+      config: [ 'button' ],
+      template: '<button {{action "foo"}}></button>'
+    }
+  ],
+
+  bad: [
+    {
+      template: '<button {{action "foo"}}></button>',
+
+      result: {
+        message: '`action` modifiers should not be used (use `onclick` attributes instead).',
+        moduleId: 'layout.hbs',
+        source: '<button {{action "foo"}}></button>',
+        line: 1,
+        column: 8,
+      },
+    },
+    {
+      template: '<a href="#" {{action "foo"}}></a>',
+
+      result: {
+        message: '`action` modifiers should not be used (use `onclick` attributes instead).',
+        moduleId: 'layout.hbs',
+        source: '<a href="#" {{action "foo"}}></a>',
+        line: 1,
+        column: 12,
+      },
+    },
+    {
+      config: [ 'button' ],
+      template: '<a href="#" {{action "foo"}}></a>',
+
+      result: {
+        message: '`action` modifiers should not be used (use `onclick` attributes instead).',
+        moduleId: 'layout.hbs',
+        source: '<a href="#" {{action "foo"}}></a>',
+        line: 1,
+        column: 12,
+      },
+    },
+  ],
+});