Repository: ember-template-lint/ember-template-lint
Pull request #865 (2019-10-19 20:44:53)

Add new `no-args-paths` rule.
https://github.com/ember-template-lint/ember-template-lint/issues/841


Patch:
diff --git a/docs/rule/no-args-paths.md b/docs/rule/no-args-paths.md
new file mode 100644
index 00000000..c24d5eab
--- /dev/null
+++ b/docs/rule/no-args-paths.md
@@ -0,0 +1,65 @@
+## no-args-paths
+
+Arguments that are passed to components are prefixed with the `@` symbol in Angle bracket syntax.
+Ember Octane leverages this in the component's templates by allowing users to directly refer to an argument using the same prefix:
+
+```hbs
+<!-- todo-list.hbs -->
+<ul>
+  {{#each @todos as |todo index|}}
+    <li>
+      {{yield (todo-item-component todo=todo) index}}
+    </li>
+  {{/each}}
+</ul>
+```
+
+We can immediately tell now by looking at this template that `@todos` is an argument that was passed to the component externally. This is in fact _always true_ - there is no way to modify the value referenced by `@todos` from the component class, it is the original, unmodified value. 
+
+### Examples
+
+This rule **forbids** the following:
+
+```hbs
+{{this.args.foo}}
+{{args.foo}}
+```
+
+```hbs
+{{my-helper this.args.foo}}
+{{my-helper (hash value=this.args.foo)}}
+```
+
+```hbs
+<MyComponent @value={{this.args.foo}} />
+<div {{my-modifier this.args.foo}}></div>
+```
+
+This rule **allows** the following:
+
+```hbs
+{{my-helper this.args}}
+{{my-helper (hash value=this.args)}}
+```
+
+```hbs
+{{@foo}}
+<MyComponent @value={{@foo}} />
+<div {{my-modifier @foo}}></div>
+```
+
+
+### Migration
+
+* find in templates `this.args.` replace to `@`
+
+### Related Rules
+
+* [no-curly-component-invocation](no-curly-component-invocation.md)
+
+### References
+
+* [RFC #276](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md)
+* [Coming Soon in Ember Octane - Part 2: Named Argument Syntax](https://www.pzuraq.com/coming-soon-in-ember-octane-part-2-angle-brackets-and-named-arguments/#namedargumentsyntax)
+* [Named arguments in Ember.js](https://www.balinterdi.com/blog/named-arguments-in-ember-js/)
+* [ember-named-arguments-polyfill](https://github.com/rwjblue/ember-named-arguments-polyfill)
\ No newline at end of file
diff --git a/docs/rule/no-curly-component-invocation.md b/docs/rule/no-curly-component-invocation.md
index 8a581a97..8f5ffd70 100644
--- a/docs/rule/no-curly-component-invocation.md
+++ b/docs/rule/no-curly-component-invocation.md
@@ -108,4 +108,8 @@ module.exports = {
     },
   },
 };
-```
\ No newline at end of file
+```
+
+### Related Rules
+
+* [no-args-paths](no-args-paths.md)
diff --git a/docs/rules.md b/docs/rules.md
index 335be933..200d3da7 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -13,6 +13,7 @@
 * [no-action](rule/no-action.md)
 * [no-action-modifiers](rule/no-action-modifiers.md)
 * [no-attrs-in-components](rule/no-attrs-in-components.md)
+* [no-args-paths](rule/no-args-paths.md)
 * [no-bare-strings](rule/no-bare-strings.md)
 * [no-debugger](rule/no-debugger.md)
 * [no-curly-component-invocation](rule/no-curly-component-invocation.md)
diff --git a/lib/rules/index.js b/lib/rules/index.js
index 0dc992a0..b0a70808 100644
--- a/lib/rules/index.js
+++ b/lib/rules/index.js
@@ -17,6 +17,7 @@ module.exports = {
   'no-action-modifiers': require('./lint-no-action-modifiers'),
   'no-attrs-in-components': require('./lint-no-attrs-in-components'),
   'no-action': require('./lint-no-action'),
+  'no-args-paths': require('./lint-no-args-paths'),
   'no-bare-strings': require('./lint-no-bare-strings'),
   'no-curly-component-invocation': require('./lint-no-curly-component-invocation'),
   'no-debugger': require('./lint-no-debugger'),
diff --git a/lib/rules/lint-no-args-paths.js b/lib/rules/lint-no-args-paths.js
new file mode 100644
index 00000000..03ec261a
--- /dev/null
+++ b/lib/rules/lint-no-args-paths.js
@@ -0,0 +1,42 @@
+'use strict';
+
+const Rule = require('./base');
+
+module.exports = class LintNoArgsPaths extends Rule {
+  visitor() {
+    const isLocal = this.isLocal.bind(this);
+    const sourceForNode = this.sourceForNode.bind(this);
+    const log = this.log.bind(this);
+    function checkPathForArgs(node) {
+      if (node.parts && node.parts[0] === 'args') {
+        if (node.data) {
+          return false;
+        }
+        if (isLocal(node)) {
+          return false;
+        }
+        return node.parts.length !== 1;
+      }
+      return false;
+    }
+    function lintArgsUsage(node) {
+      const possibleLintError = checkPathForArgs(node);
+      if (possibleLintError === true) {
+        log({
+          message: `Component templates should avoid "${
+            node.original
+          }" usage, try "@${node.parts.slice(1).join('.')}" instead.`,
+          line: node.loc && node.loc.start.line,
+          column: node.loc && node.loc.start.column,
+          source: sourceForNode(node),
+        });
+      }
+    }
+
+    return {
+      PathExpression(node) {
+        lintArgsUsage(node);
+      },
+    };
+  }
+};


Test patch:
diff --git a/test/unit/rules/lint-no-args-paths-test.js b/test/unit/rules/lint-no-args-paths-test.js
new file mode 100644
index 00000000..a99de14f
--- /dev/null
+++ b/test/unit/rules/lint-no-args-paths-test.js
@@ -0,0 +1,93 @@
+'use strict';
+
+const generateRuleTests = require('../../helpers/rule-test-harness');
+
+generateRuleTests({
+  name: 'no-args-paths',
+
+  config: 'true',
+  good: [
+    '<div @foo={{cleanup this.args}}></div>',
+    '{{foo (name this.args)}}',
+    '{{foo name=this.args}}',
+    '{{foo name=(extract this.args)}}',
+    '<Foo @params={{this.args}} />',
+    '<Foo {{mod this.args}} />',
+    '<Foo {{mod items=this.args}} />',
+    '<Foo {{mod items=(extract this.args)}} />',
+  ],
+  bad: [
+    {
+      template: '{{hello (format value=args.foo)}}',
+      result: {
+        message: 'Component templates should avoid "args.foo" usage, try "@foo" instead.',
+        source: 'args.foo',
+        line: 1,
+        column: 22,
+      },
+    },
+    {
+      template: '{{hello value=args.foo}}',
+      result: {
+        message: 'Component templates should avoid "args.foo" usage, try "@foo" instead.',
+        source: 'args.foo',
+        line: 1,
+        column: 14,
+      },
+    },
+    {
+      template: '{{hello (format args.foo.bar)}}',
+      result: {
+        message: 'Component templates should avoid "args.foo.bar" usage, try "@foo.bar" instead.',
+        source: 'args.foo.bar',
+        line: 1,
+        column: 16,
+      },
+    },
+    {
+      template: '<br {{hello args.foo.bar}}>',
+      result: {
+        message: 'Component templates should avoid "args.foo.bar" usage, try "@foo.bar" instead.',
+        source: 'args.foo.bar',
+        line: 1,
+        column: 12,
+      },
+    },
+    {
+      template: '{{hello args.foo.bar}}',
+      result: {
+        message: 'Component templates should avoid "args.foo.bar" usage, try "@foo.bar" instead.',
+        source: 'args.foo.bar',
+        line: 1,
+        column: 8,
+      },
+    },
+    {
+      template: '{{args.foo.bar}}',
+      result: {
+        message: 'Component templates should avoid "args.foo.bar" usage, try "@foo.bar" instead.',
+        source: 'args.foo.bar',
+        line: 1,
+        column: 2,
+      },
+    },
+    {
+      template: '{{args.foo}}',
+      result: {
+        message: 'Component templates should avoid "args.foo" usage, try "@foo" instead.',
+        source: 'args.foo',
+        line: 1,
+        column: 2,
+      },
+    },
+    {
+      template: '{{this.args.foo}}',
+      result: {
+        message: 'Component templates should avoid "this.args.foo" usage, try "@foo" instead.',
+        source: 'this.args.foo',
+        line: 1,
+        column: 2,
+      },
+    },
+  ],
+});