Skip to content

Commit 0577eb3

Browse files
committed
Extract rule: template-no-route-action
1 parent 33af5b4 commit 0577eb3

4 files changed

Lines changed: 423 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: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
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+
Disallows the use of the `{{route-action}}` helper.
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+
Examples of **incorrect** code for this rule:
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+
```hbs
34+
{{route-action 'save'}}
35+
```
36+
37+
```hbs
38+
<button {{on 'click' (route-action 'save')}}>Save</button>
39+
```
40+
41+
Examples of **correct** code for this rule:
42+
43+
```hbs
44+
<CustomComponent @onUpdate={{this.updateFoo}} />
45+
```
46+
47+
```hbs
48+
<CustomComponent @onUpdate={{fn this.updateFoo 'bar'}} />
49+
```
50+
51+
```hbs
52+
{{custom-component onUpdate=this.updateFoo}}
53+
```
54+
55+
```hbs
56+
{{custom-component onUpdate=(fn this.updateFoo 'bar')}}
57+
```
58+
59+
```hbs
60+
<button {{on 'click' (fn this.save)}}>Save</button>
61+
```
62+
63+
```hbs
64+
<button {{on 'click' this.handleClick}}>Click</button>
65+
```
66+
67+
## Migration
68+
69+
The example below shows how to migrate from route-action to controller actions.
70+
71+
### Before
72+
73+
```js
74+
// app/routes/posts.js
75+
export default class extends Route {
76+
model(params) {
77+
return this.store.query('post', { page: params.page });
78+
}
79+
80+
@action
81+
goToPage(pageNum) {
82+
this.transitionTo({ queryParams: { page: pageNum } });
83+
}
84+
}
85+
```
86+
87+
```js
88+
// app/controllers/posts.js
89+
export default class extends Controller {
90+
queryParams = ['page'];
91+
page = 1;
92+
}
93+
```
94+
95+
```hbs
96+
{{#each @model as |post|}}
97+
<Post @title={{post.title}} @content={{post.content}} />
98+
{{/each}}
99+
100+
<button {{action (route-action 'goToPage' 1)}}>1</button>
101+
<button {{action (route-action 'goToPage' 2)}}>2</button>
102+
<button {{action (route-action 'goToPage' 3)}}>3</button>
103+
```
104+
105+
### After
106+
107+
```js
108+
// app/routes/posts.js
109+
export default class extends Route {
110+
model(params) {
111+
return this.store.query('post', { page: params.page });
112+
}
113+
}
114+
```
115+
116+
```js
117+
// app/controllers/posts.js
118+
export default class extends Controller {
119+
queryParams = ['page'];
120+
page = 1;
121+
122+
@action
123+
goToPage(pageNum) {
124+
this.transitionToRoute({ queryParams: { page: pageNum } });
125+
}
126+
}
127+
```
128+
129+
```hbs
130+
{{#each @model as |post|}}
131+
<Post @title={{post.title}} @content={{post.content}} />
132+
{{/each}}
133+
134+
<button {{on 'click' (fn this.goToPage 1)}}>1</button>
135+
<button {{on 'click' (fn this.goToPage 2)}}>2</button>
136+
<button {{on 'click' (fn this.goToPage 3)}}>3</button>
137+
```
138+
139+
## References
140+
141+
- [ember-route-action-helper](https://github.com/DockYard/ember-route-action-helper)
142+
- [Ember guides/Controllers](https://guides.emberjs.com/release/routing/controllers/)
143+
- [Ember Best Practices: What are controllers good for?](https://dockyard.com/blog/2017/06/16/ember-best-practices-what-are-controllers-good-for)
144+
- [Ember.js Guides - Actions](https://guides.emberjs.com/release/components/component-state-and-actions/)
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)