Repository: ember-template-lint/ember-template-lint
Pull request #416 (2018-05-28 10:48:06)

Add `no-attr-in-components` rule
https://github.com/ember-template-lint/ember-template-lint/issues/384

rwjblue: I think it will also need a rebase once the main changes are done...

Patch:
diff --git a/docs/rule/no-attrs-in-components.md b/docs/rule/no-attrs-in-components.md
new file mode 100644
index 00000000..f598738b
--- /dev/null
+++ b/docs/rule/no-attrs-in-components.md
@@ -0,0 +1,25 @@
+## no-attrs-in-components
+
+This rule prevents the usage of `attrs` property to access values passed to the component since all the values can be accessed directly from the from the template.
+
+For example, doing
+
+```components/templates/layout.hbs
+{{attr.foo}}
+```
+is bad. The correct way would be to directly consume `foo` in the template like,
+
+```components/templates/layout.hbs
+{{foo}}
+```
+
+or if you using Ember 3.1 and above,
+
+```components/templates/layout.hbs
+{{@foo}}
+```
+
+
+The following values are valid configuration:
+
+  * boolean -- `true` for enabled / `false` for disabled
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 37759299..c45a6daa 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -36,4 +36,5 @@ module.exports = {
   'no-unnecessary-concat': require('./lint-no-unnecessary-concat'),
   'no-partial': require('./lint-no-partial'),
   'invocable-blacklist': require('./lint-invocable-blacklist'),
+  'no-attr-in-components': require('./lint-no-attr-in-components'),
 };
diff --git a/lib/rules/lint-no-attr-in-components.js b/lib/rules/lint-no-attr-in-components.js
new file mode 100644
index 00000000..0f98fdb4
--- /dev/null
+++ b/lib/rules/lint-no-attr-in-components.js
@@ -0,0 +1,27 @@
+'use strict';
+
+const Rule = require('./base');
+const componentTemplateRegex = new RegExp('templates/components|components/.*/template|ui/components|-components/');
+
+module.exports = class LinkNoAttrComponent extends Rule {
+  isComponentTemplate() {
+    return componentTemplateRegex.test(this.templateEnvironmentData.moduleName);
+  }
+  visitor() {
+    return {
+      PathExpression(node) {
+        if (!this.isComponentTemplate()) {
+          return;
+        }
+        if (node.parts && node.parts[0] === 'attrs') {
+          this.log({
+            message: 'Component templates should not contain `attrs`.',
+            line: node.loc && node.loc.start.line,
+            column: node.loc && node.loc.start.column,
+            source: this.sourceForNode(node)
+          });
+        }
+      }
+    };
+  }
+};


Test patch:
diff --git a/test/unit/rules/lint-no-attr-in-components-test.js b/test/unit/rules/lint-no-attr-in-components-test.js
new file mode 100644
index 00000000..456b03a2
--- /dev/null
+++ b/test/unit/rules/lint-no-attr-in-components-test.js
@@ -0,0 +1,129 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'no-attr-in-components',
+
+  config: 'true',
+  good: [
+    '<div></div>',
+    '{{foo}}',
+    '<div>{{foo.bar}}</div>',
+    '{{attrs.foo}}', // defaults to layout.hbs
+  ],
+  bad: [
+    {
+      template: '{{attrs.foo}}',
+
+      meta: {
+        moduleId: 'templates/components/layout.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'templates/components/layout.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 2,
+      }
+    },
+    {
+      template: '<div class={{attrs.foo}}></div>',
+
+      meta: {
+        moduleId: 'templates/components/layout.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'templates/components/layout.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 13,
+      }
+    },
+    {
+      template: '{{#if attrs.foo}}bar{{/if}}',
+
+      meta: {
+        moduleId: 'templates/components/layout.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'templates/components/layout.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 6,
+      }
+    },
+    {
+      template: '{{bar foo=attrs.foo}}',
+
+      meta: {
+        moduleId: 'templates/components/layout.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'templates/components/layout.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 10,
+      }
+    },
+    {
+      template: '{{component attrs.foo}}',
+
+      meta: {
+        moduleId: 'templates/components/layout.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'templates/components/layout.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 12,
+      }
+    },
+    {
+      template: '{{bar/baz (hash foo=attrs.foo)}}',
+
+      meta: {
+        moduleId: 'templates/components/layout.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'templates/components/layout.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 20,
+      }
+    },
+    {
+      template: '{{attrs.foo}}',
+
+      meta: {
+        moduleId: 'components/comment/template.hbs',
+      },
+
+      result: {
+        rule: 'no-attr-in-components',
+        message: 'Component templates should not contain `attrs`.',
+        moduleId: 'components/comment/template.hbs',
+        source: 'attrs.foo',
+        line: 1,
+        column: 2,
+      }
+    }
+  ]
+});