Skip to content

Commit b054aa5

Browse files
Merge pull request #2571 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-route-action
Extract rule: template-no-route-action
2 parents 33af5b4 + c178d29 commit b054aa5

4 files changed

Lines changed: 430 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ rules in templates can be disabled with eslint directives with mustache or html
343343
| [template-no-attrs-in-components](docs/rules/template-no-attrs-in-components.md) | disallow attrs in component templates | | | |
344344
| [template-no-link-to-positional-params](docs/rules/template-no-link-to-positional-params.md) | disallow positional params in LinkTo component | | | |
345345
| [template-no-link-to-tagname](docs/rules/template-no-link-to-tagname.md) | disallow tagName attribute on LinkTo component | | | |
346+
| [template-no-route-action](docs/rules/template-no-route-action.md) | disallow usage of route-action helper | | | |
346347
| [template-no-unbound](docs/rules/template-no-unbound.md) | disallow {{unbound}} helper | | | |
347348
| [template-no-with](docs/rules/template-no-with.md) | disallow {{with}} helper | | | |
348349

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
# ember/template-no-route-action
2+
3+
> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.
4+
5+
<!-- end auto-generated rule header -->
6+
7+
This rule disallows the usage of `route-action`.
8+
9+
[ember-route-action-helper](https://github.com/DockYard/ember-route-action-helper) was a popular addon used to add actions to a route without creating a separate controller. Given the changes in Ember since ember-route-action-helper was a widely used pattern, controllers are now encouraged and we want to discourage the use of route-action.
10+
11+
Most route actions should either be sent to the controller first or encapsulated within a downstream component instead. We should never be escaping the DDAU hierarchy to lob actions up to the route.
12+
13+
## Examples
14+
15+
This rule **forbids** the following:
16+
17+
```hbs
18+
<CustomComponent @onUpdate={{route-action 'updateFoo'}} />
19+
```
20+
21+
```hbs
22+
<CustomComponent @onUpdate={{route-action 'updateFoo' 'bar'}} />
23+
```
24+
25+
```hbs
26+
{{custom-component onUpdate=(route-action 'updateFoo')}}
27+
```
28+
29+
```hbs
30+
{{custom-component onUpdate=(route-action 'updateFoo' 'bar')}}
31+
```
32+
33+
With the given route:
34+
35+
```js
36+
// app/routes/foo.js
37+
export default class extends Route {
38+
@action
39+
updateFoo(baz) {
40+
// ...
41+
}
42+
}
43+
```
44+
45+
This rule **allows** the following:
46+
47+
```hbs
48+
<CustomComponent @onUpdate={{this.updateFoo}} />
49+
```
50+
51+
```hbs
52+
<CustomComponent @onUpdate={{fn this.updateFoo 'bar'}} />
53+
```
54+
55+
```hbs
56+
{{custom-component onUpdate=this.updateFoo}}
57+
```
58+
59+
```hbs
60+
{{custom-component onUpdate=(fn this.updateFoo 'bar')}}
61+
```
62+
63+
With the given controller:
64+
65+
```js
66+
// app/controllers/foo.js
67+
export default class extends Controller {
68+
@action
69+
updateFoo(baz) {
70+
// ...
71+
}
72+
}
73+
```
74+
75+
## Migration
76+
77+
The example below shows how to migrate from route-action to controller actions.
78+
79+
### Before
80+
81+
```js
82+
// app/routes/posts.js
83+
export default class extends Route {
84+
model(params) {
85+
return this.store.query('post', { page: params.page });
86+
}
87+
88+
@action
89+
goToPage(pageNum) {
90+
this.transitionTo({ queryParams: { page: pageNum } });
91+
}
92+
}
93+
```
94+
95+
```js
96+
// app/controllers/posts.js
97+
export default class extends Controller {
98+
queryParams = ['page'];
99+
page = 1;
100+
}
101+
```
102+
103+
```hbs
104+
{{#each @model as |post|}}
105+
<Post @title={{post.title}} @content={{post.content}} />
106+
{{/each}}
107+
108+
<button {{action (route-action 'goToPage' 1)}}>1</button>
109+
<button {{action (route-action 'goToPage' 2)}}>2</button>
110+
<button {{action (route-action 'goToPage' 3)}}>3</button>
111+
```
112+
113+
### After
114+
115+
```js
116+
// app/routes/posts.js
117+
export default class extends Route {
118+
model(params) {
119+
return this.store.query('post', { page: params.page });
120+
}
121+
}
122+
```
123+
124+
```js
125+
// app/controllers/posts.js
126+
export default class extends Controller {
127+
queryParams = ['page'];
128+
page = 1;
129+
130+
@action
131+
goToPage(pageNum) {
132+
this.transitionToRoute({ queryParams: { page: pageNum } });
133+
}
134+
}
135+
```
136+
137+
```hbs
138+
{{#each @model as |post|}}
139+
<Post @title={{post.title}} @content={{post.content}} />
140+
{{/each}}
141+
142+
<button {{on 'click' (fn this.goToPage 1)}}>1</button>
143+
<button {{on 'click' (fn this.goToPage 2)}}>2</button>
144+
<button {{on 'click' (fn this.goToPage 3)}}>3</button>
145+
```
146+
147+
## References
148+
149+
- [ember-route-action-helper](https://github.com/DockYard/ember-route-action-helper)
150+
- [Ember guides/Controllers](https://guides.emberjs.com/release/routing/controllers/)
151+
- [Ember Best Practices: What are controllers good for?](https://dockyard.com/blog/2017/06/16/ember-best-practices-what-are-controllers-good-for)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow usage of route-action helper',
7+
category: 'Deprecations',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-route-action.md',
9+
templateMode: 'loose',
10+
},
11+
fixable: null,
12+
schema: [],
13+
messages: {
14+
unexpected:
15+
'Do not use the (route-action) helper. Use the (fn) helper or closure actions instead.',
16+
},
17+
originallyFrom: {
18+
name: 'ember-template-lint',
19+
rule: 'lib/rules/no-route-action.js',
20+
docs: 'docs/rule/no-route-action.md',
21+
tests: 'test/unit/rules/no-route-action-test.js',
22+
},
23+
},
24+
25+
create(context) {
26+
function checkForRouteAction(node) {
27+
if (
28+
node.path &&
29+
node.path.type === 'GlimmerPathExpression' &&
30+
node.path.original === 'route-action'
31+
) {
32+
context.report({
33+
node,
34+
messageId: 'unexpected',
35+
});
36+
}
37+
}
38+
39+
return {
40+
GlimmerMustacheStatement(node) {
41+
checkForRouteAction(node);
42+
},
43+
44+
GlimmerSubExpression(node) {
45+
checkForRouteAction(node);
46+
},
47+
};
48+
},
49+
};

0 commit comments

Comments
 (0)