Skip to content

Commit b347140

Browse files
committed
Extract rule: template-no-redundant-fn
1 parent ba63d48 commit b347140

4 files changed

Lines changed: 277 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ rules in templates can be disabled with eslint directives with mustache or html
261261
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
262262
| [template-no-positional-data-test-selectors](docs/rules/template-no-positional-data-test-selectors.md) | disallow positional data-test-* params in curly invocations | | | |
263263
| [template-no-potential-path-strings](docs/rules/template-no-potential-path-strings.md) | disallow potential path strings in attribute values | | | |
264+
| [template-no-redundant-fn](docs/rules/template-no-redundant-fn.md) | disallow unnecessary usage of (fn) helper | | | |
264265
| [template-no-restricted-invocations](docs/rules/template-no-restricted-invocations.md) | disallow certain components, helpers or modifiers from being used | | | |
265266
| [template-no-splattributes-with-class](docs/rules/template-no-splattributes-with-class.md) | disallow splattributes with class attribute | | | |
266267
| [template-no-this-in-template-only-components](docs/rules/template-no-this-in-template-only-components.md) | disallow this in template-only components (gjs/gts) | | 🔧 | |
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# ember/template-no-redundant-fn
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallows unnecessary usage of the `{{fn}}` helper.
6+
7+
When `{{fn}}` is used with only a function reference and no arguments to curry, it's redundant. You can pass the function directly.
8+
9+
## Rule Details
10+
11+
This rule detects when `{{fn}}` is called with only one argument (the function itself) and no curried arguments.
12+
13+
## Examples
14+
15+
Examples of **incorrect** code for this rule:
16+
17+
```gjs
18+
<template>
19+
<button {{on "click" (fn this.handleClick)}}>Click</button>
20+
</template>
21+
```
22+
23+
```gjs
24+
<template>
25+
<Component @action={{fn this.save}} />
26+
</template>
27+
```
28+
29+
Examples of **correct** code for this rule:
30+
31+
```gjs
32+
<template>
33+
<button {{on "click" this.handleClick}}>Click</button>
34+
</template>
35+
```
36+
37+
```gjs
38+
<template>
39+
<button {{on "click" (fn this.handleClick arg)}}>Click</button>
40+
</template>
41+
```
42+
43+
```gjs
44+
<template>
45+
<Component @action={{this.save}} />
46+
</template>
47+
```
48+
49+
## Migration
50+
51+
Replace:
52+
53+
```gjs
54+
<button {{on "click" (fn this.action)}}>
55+
```
56+
57+
With:
58+
59+
```gjs
60+
<button {{on "click" this.action}}>
61+
```
62+
63+
## References
64+
65+
- [eslint-plugin-ember template-no-redundant-fn](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-redundant-fn.md)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'disallow unnecessary usage of (fn) helper',
7+
category: 'Best Practices',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-redundant-fn.md',
9+
templateMode: 'both',
10+
},
11+
fixable: null,
12+
schema: [],
13+
messages: {
14+
redundant:
15+
'Unnecessary use of (fn) helper. Pass the function directly instead: {{suggestion}}',
16+
},
17+
originallyFrom: {
18+
name: 'ember-template-lint',
19+
rule: 'lib/rules/no-redundant-fn.js',
20+
docs: 'docs/rule/no-redundant-fn.md',
21+
tests: 'test/unit/rules/no-redundant-fn-test.js',
22+
},
23+
},
24+
25+
create(context) {
26+
function checkFnUsage(node) {
27+
// Check if this is an (fn) call with only one argument (the function itself)
28+
if (
29+
node.path &&
30+
node.path.type === 'GlimmerPathExpression' &&
31+
node.path.original === 'fn' &&
32+
node.params &&
33+
node.params.length === 1 &&
34+
!node.hash?.pairs?.length
35+
) {
36+
const param = node.params[0];
37+
const paramText =
38+
param.type === 'GlimmerPathExpression'
39+
? param.original
40+
: context.sourceCode.getText(param);
41+
42+
context.report({
43+
node,
44+
messageId: 'redundant',
45+
data: {
46+
suggestion: paramText,
47+
},
48+
});
49+
}
50+
}
51+
52+
return {
53+
GlimmerSubExpression(node) {
54+
checkFnUsage(node);
55+
},
56+
57+
GlimmerMustacheStatement(node) {
58+
checkFnUsage(node);
59+
},
60+
};
61+
},
62+
};
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/template-no-redundant-fn');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
//------------------------------------------------------------------------------
9+
// Tests
10+
//------------------------------------------------------------------------------
11+
12+
const ruleTester = new RuleTester({
13+
parser: require.resolve('ember-eslint-parser'),
14+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
15+
});
16+
17+
ruleTester.run('template-no-redundant-fn', rule, {
18+
valid: [
19+
`<template>
20+
<button {{on "click" this.handleClick}}>Click</button>
21+
</template>`,
22+
`<template>
23+
<button {{on "click" (fn this.handleClick arg)}}>Click</button>
24+
</template>`,
25+
`<template>
26+
<button {{on "click" (fn this.handleClick arg1 arg2)}}>Click</button>
27+
</template>`,
28+
`<template>
29+
<Component @action={{this.myAction}} />
30+
</template>`,
31+
32+
'<template><button {{on "click" this.handleClick}}>Click Me</button></template>',
33+
'<template><button {{on "click" (fn this.handleClick "foo")}}>Click Me</button></template>',
34+
'<template><SomeComponent @onClick={{this.handleClick}} /></template>',
35+
'<template><SomeComponent @onClick={{fn this.handleClick "foo"}} /></template>',
36+
'<template>{{foo bar=this.handleClick}}></template>',
37+
'<template>{{foo bar=(fn this.handleClick "foo")}}></template>',
38+
],
39+
40+
invalid: [
41+
{
42+
code: `<template>
43+
<button {{on "click" (fn this.handleClick)}}>Click</button>
44+
</template>`,
45+
output: null,
46+
errors: [
47+
{
48+
message:
49+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
50+
type: 'GlimmerSubExpression',
51+
},
52+
],
53+
},
54+
{
55+
code: `<template>
56+
<Component @action={{fn this.save}} />
57+
</template>`,
58+
output: null,
59+
errors: [
60+
{
61+
message: 'Unnecessary use of (fn) helper. Pass the function directly instead: this.save',
62+
type: 'GlimmerMustacheStatement',
63+
},
64+
],
65+
},
66+
67+
{
68+
code: '<template><button {{on "click" (fn this.handleClick)}}>Click Me</button></template>',
69+
output: null,
70+
errors: [
71+
{
72+
message:
73+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
74+
},
75+
],
76+
},
77+
{
78+
code: '<template><SomeComponent @onClick={{fn this.handleClick}} /></template>',
79+
output: null,
80+
errors: [
81+
{
82+
message:
83+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
84+
},
85+
],
86+
},
87+
{
88+
code: '<template>{{foo bar=(fn this.handleClick)}}></template>',
89+
output: null,
90+
errors: [
91+
{
92+
message:
93+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
94+
},
95+
],
96+
},
97+
],
98+
});
99+
100+
const hbsRuleTester = new RuleTester({
101+
parser: require.resolve('ember-eslint-parser/hbs'),
102+
parserOptions: {
103+
ecmaVersion: 2022,
104+
sourceType: 'module',
105+
},
106+
});
107+
108+
hbsRuleTester.run('template-no-redundant-fn', rule, {
109+
valid: [
110+
'<button {{on "click" this.handleClick}}>Click Me</button>',
111+
'<button {{on "click" (fn this.handleClick "foo")}}>Click Me</button>',
112+
'<SomeComponent @onClick={{this.handleClick}} />',
113+
'<SomeComponent @onClick={{fn this.handleClick "foo"}} />',
114+
'{{foo bar=this.handleClick}}>',
115+
'{{foo bar=(fn this.handleClick "foo")}}>',
116+
],
117+
invalid: [
118+
{
119+
code: '<button {{on "click" (fn this.handleClick)}}>Click Me</button>',
120+
output: null,
121+
errors: [
122+
{
123+
message:
124+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
125+
},
126+
],
127+
},
128+
{
129+
code: '<SomeComponent @onClick={{fn this.handleClick}} />',
130+
output: null,
131+
errors: [
132+
{
133+
message:
134+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
135+
},
136+
],
137+
},
138+
{
139+
code: '{{foo bar=(fn this.handleClick)}}>',
140+
output: null,
141+
errors: [
142+
{
143+
message:
144+
'Unnecessary use of (fn) helper. Pass the function directly instead: this.handleClick',
145+
},
146+
],
147+
},
148+
],
149+
});

0 commit comments

Comments
 (0)