Skip to content

Commit 77fbe79

Browse files
committed
Extract rule: template-no-args-paths
1 parent cdb0f91 commit 77fbe79

4 files changed

Lines changed: 244 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ rules in templates can be disabled with eslint directives with mustache or html
202202
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
203203
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
204204
| [template-no-action-on-submit-button](docs/rules/template-no-action-on-submit-button.md) | disallow action attribute on submit buttons | | | |
205+
| [template-no-args-paths](docs/rules/template-no-args-paths.md) | disallow @args in paths | | | |
205206
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
206207
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
207208
| [template-no-at-ember-render-modifiers](docs/rules/template-no-at-ember-render-modifiers.md) | disallow usage of @ember/render-modifiers | | | |
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# ember/template-no-args-paths
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Arguments that are passed to components are prefixed with the `@` symbol in Angle bracket syntax.
6+
Ember Octane leverages this in the component's templates by allowing users to directly refer to an argument using the same prefix:
7+
8+
```gjs
9+
<template>
10+
<!-- todo-list.hbs -->
11+
<ul>
12+
{{#each @todos as |todo index|}}
13+
<li>
14+
{{yield (todo-item-component todo=todo) index}}
15+
</li>
16+
{{/each}}
17+
</ul>
18+
</template>
19+
```
20+
21+
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.
22+
23+
## Examples
24+
25+
This rule **forbids** the following:
26+
27+
```gjs
28+
<template>
29+
{{this.args.foo}}
30+
{{args.foo}}
31+
</template>
32+
```
33+
34+
```gjs
35+
<template>
36+
{{my-helper this.args.foo}}
37+
{{my-helper (hash value=this.args.foo)}}
38+
</template>
39+
```
40+
41+
```gjs
42+
<template>
43+
<MyComponent @value={{this.args.foo}} />
44+
<div {{my-modifier this.args.foo}}></div>
45+
</template>
46+
```
47+
48+
This rule **allows** the following:
49+
50+
```gjs
51+
<template>
52+
{{my-helper this.args}}
53+
{{my-helper (hash value=this.args)}}
54+
</template>
55+
```
56+
57+
```gjs
58+
<template>
59+
{{@foo}}
60+
<MyComponent @value={{@foo}} />
61+
<div {{my-modifier @foo}}></div>
62+
</template>
63+
```
64+
65+
## Migration
66+
67+
- find in templates `this.args.` replace to `@`
68+
69+
## Related Rules
70+
71+
- [no-curly-component-invocation](no-curly-component-invocation.md)
72+
73+
## References
74+
75+
- [RFC #276](https://github.com/emberjs/rfcs/blob/master/text/0276-named-args.md)
76+
- [Coming Soon in Ember Octane - Part 2: Named Argument Syntax](https://www.pzuraq.com/blog/coming-soon-in-ember-octane-part-2-angle-brackets-and-named-arguments/#namedargumentsyntax)
77+
- [Named arguments in Ember.js](https://www.balinterdi.com/blog/named-arguments-in-ember-js/)
78+
- [ember-named-arguments-polyfill](https://github.com/rwjblue/ember-named-arguments-polyfill)
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow @args in paths',
7+
category: 'Best Practices',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-args-paths.md',
9+
templateMode: 'both',
10+
},
11+
schema: [],
12+
messages: { argsPath: 'Do not use paths with @args, use @argName directly instead.' },
13+
originallyFrom: {
14+
name: 'ember-template-lint',
15+
rule: 'lib/rules/no-args-paths.js',
16+
docs: 'docs/rule/no-args-paths.md',
17+
tests: 'test/unit/rules/no-args-paths-test.js',
18+
},
19+
},
20+
create(context) {
21+
return {
22+
GlimmerPathExpression(node) {
23+
const path = node.original;
24+
if (
25+
path?.startsWith('@args.') ||
26+
path?.startsWith('args.') ||
27+
path?.startsWith('this.args.')
28+
) {
29+
context.report({ node, messageId: 'argsPath' });
30+
}
31+
},
32+
};
33+
},
34+
};
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
const rule = require('../../../lib/rules/template-no-args-paths');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const ruleTester = new RuleTester({
5+
parser: require.resolve('ember-eslint-parser'),
6+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
7+
});
8+
ruleTester.run('template-no-args-paths', rule, {
9+
valid: [
10+
'<template>{{@foo}}</template>',
11+
'<template><div @foo={{cleanup this.args}}></div></template>',
12+
'<template>{{foo (name this.args)}}</template>',
13+
'<template>{{foo name=this.args}}</template>',
14+
'<template>{{foo name=(extract this.args)}}</template>',
15+
'<template><Foo @params={{this.args}} /></template>',
16+
'<template><Foo {{mod this.args}} /></template>',
17+
'<template><Foo {{mod items=this.args}} /></template>',
18+
'<template><Foo {{mod items=(extract this.args)}} /></template>',
19+
],
20+
invalid: [
21+
{
22+
code: '<template>{{@args.foo}}</template>',
23+
output: null,
24+
errors: [{ messageId: 'argsPath' }],
25+
},
26+
27+
{
28+
code: '<template>{{hello (format value=args.foo)}}</template>',
29+
output: null,
30+
errors: [{ messageId: 'argsPath' }],
31+
},
32+
{
33+
code: '<template>{{hello value=args.foo}}</template>',
34+
output: null,
35+
errors: [{ messageId: 'argsPath' }],
36+
},
37+
{
38+
code: '<template>{{hello (format args.foo.bar)}}</template>',
39+
output: null,
40+
errors: [{ messageId: 'argsPath' }],
41+
},
42+
{
43+
code: '<template><br {{hello args.foo.bar}}></template>',
44+
output: null,
45+
errors: [{ messageId: 'argsPath' }],
46+
},
47+
{
48+
code: '<template>{{hello args.foo.bar}}</template>',
49+
output: null,
50+
errors: [{ messageId: 'argsPath' }],
51+
},
52+
{
53+
code: '<template>{{args.foo.bar}}</template>',
54+
output: null,
55+
errors: [{ messageId: 'argsPath' }],
56+
},
57+
{
58+
code: '<template>{{args.foo}}</template>',
59+
output: null,
60+
errors: [{ messageId: 'argsPath' }],
61+
},
62+
{
63+
code: '<template>{{this.args.foo}}</template>',
64+
output: null,
65+
errors: [{ messageId: 'argsPath' }],
66+
},
67+
],
68+
});
69+
70+
const hbsRuleTester = new RuleTester({
71+
parser: require.resolve('ember-eslint-parser/hbs'),
72+
parserOptions: {
73+
ecmaVersion: 2022,
74+
sourceType: 'module',
75+
},
76+
});
77+
78+
hbsRuleTester.run('template-no-args-paths', rule, {
79+
valid: [
80+
'<div @foo={{cleanup this.args}}></div>',
81+
'{{foo (name this.args)}}',
82+
'{{foo name=this.args}}',
83+
'{{foo name=(extract this.args)}}',
84+
'<Foo @params={{this.args}} />',
85+
'<Foo {{mod this.args}} />',
86+
'<Foo {{mod items=this.args}} />',
87+
'<Foo {{mod items=(extract this.args)}} />',
88+
],
89+
invalid: [
90+
{
91+
code: '{{hello (format value=args.foo)}}',
92+
output: null,
93+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
94+
},
95+
{
96+
code: '{{hello value=args.foo}}',
97+
output: null,
98+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
99+
},
100+
{
101+
code: '{{hello (format args.foo.bar)}}',
102+
output: null,
103+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
104+
},
105+
{
106+
code: '<br {{hello args.foo.bar}}>',
107+
output: null,
108+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
109+
},
110+
{
111+
code: '{{hello args.foo.bar}}',
112+
output: null,
113+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
114+
},
115+
{
116+
code: '{{args.foo.bar}}',
117+
output: null,
118+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
119+
},
120+
{
121+
code: '{{args.foo}}',
122+
output: null,
123+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
124+
},
125+
{
126+
code: '{{this.args.foo}}',
127+
output: null,
128+
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
129+
},
130+
],
131+
});

0 commit comments

Comments
 (0)