Skip to content

Commit 0149ef1

Browse files
Merge pull request #2429 from NullVoxPopuli/nvp/template-lint-extract-rule-template-no-element-event-actions
Extract rule: template-no-element-event-actions
2 parents 285d8ba + b154215 commit 0149ef1

File tree

4 files changed

+195
-0
lines changed

4 files changed

+195
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ rules in templates can be disabled with eslint directives with mustache or html
198198
| [template-no-block-params-for-html-elements](docs/rules/template-no-block-params-for-html-elements.md) | disallow block params on HTML elements | | | |
199199
| [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md) | disallow capital arguments (use lowercase @arg instead of @Arg) | | | |
200200
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
201+
| [template-no-element-event-actions](docs/rules/template-no-element-event-actions.md) | disallow element event actions (use {{on}} modifier instead) | | | |
201202
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
202203

203204
### Components
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# ember/template-no-element-event-actions
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow using element event actions (e.g., `onclick={{action}}`) in templates. Use the `{{on}}` modifier instead.
6+
7+
## Rule Details
8+
9+
This rule disallows the use of element event actions in templates.
10+
11+
## Examples
12+
13+
Examples of **incorrect** code for this rule:
14+
15+
```gjs
16+
<template>
17+
<button onclick={{this.handleClick}}>Click</button>
18+
</template>
19+
```
20+
21+
```gjs
22+
<template>
23+
<div onmouseenter={{this.handleHover}}>Hover</div>
24+
</template>
25+
```
26+
27+
Examples of **correct** code for this rule:
28+
29+
```gjs
30+
<template>
31+
<button {{on "click" this.handleClick}}>Click</button>
32+
</template>
33+
```
34+
35+
```gjs
36+
<template>
37+
<div {{on "mouseenter" this.handleHover}}>Hover</div>
38+
</template>
39+
```
40+
41+
## Options
42+
43+
| Name | Type | Default | Description |
44+
| --------------------- | --------- | ------- | ----------------------------------------------------------------------------------------------------------------- |
45+
| `requireActionHelper` | `boolean` | `false` | When `true`, only flags events using `{{action ...}}`; when `false`, flags any dynamic value on event attributes. |
46+
47+
## References
48+
49+
- [Ember Octane migration guide](https://guides.emberjs.com/release/upgrading/current-edition/action-on-and-fn/)
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'disallow element event actions (use {{on}} modifier instead)',
7+
category: 'Best Practices',
8+
recommended: false,
9+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-element-event-actions.md',
10+
},
11+
fixable: null,
12+
schema: [
13+
{
14+
type: 'object',
15+
properties: {
16+
requireActionHelper: { type: 'boolean' },
17+
},
18+
additionalProperties: false,
19+
},
20+
],
21+
messages: {
22+
noElementEventActions: 'Do not use element event actions. Use the `on` modifier instead.',
23+
},
24+
strictGjs: true,
25+
strictGts: true,
26+
},
27+
28+
create(context) {
29+
const options = context.options[0] || {};
30+
const requireActionHelper = options.requireActionHelper || false;
31+
32+
return {
33+
GlimmerElementNode(node) {
34+
if (!node.attributes) {
35+
return;
36+
}
37+
38+
for (const attr of node.attributes) {
39+
if (attr.type !== 'GlimmerAttrNode' || !attr.name) {
40+
continue;
41+
}
42+
const name = attr.name.toLowerCase();
43+
if (!name.startsWith('on')) {
44+
continue;
45+
}
46+
// Skip non-event attributes like "once", "open", etc.
47+
if (name.length <= 2) {
48+
continue;
49+
}
50+
51+
// If requireActionHelper is true, only flag when the value uses {{action ...}}
52+
if (requireActionHelper) {
53+
if (
54+
attr.value?.type === 'GlimmerMustacheStatement' &&
55+
attr.value.path?.original === 'action'
56+
) {
57+
context.report({ node: attr, messageId: 'noElementEventActions' });
58+
}
59+
} else {
60+
// Flag any mustache value on event attributes
61+
if (
62+
attr.value?.type === 'GlimmerMustacheStatement' ||
63+
attr.value?.type === 'GlimmerConcatStatement'
64+
) {
65+
context.report({ node: attr, messageId: 'noElementEventActions' });
66+
}
67+
}
68+
}
69+
},
70+
};
71+
},
72+
};
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
const { RuleTester } = require('eslint');
2+
const rule = require('../../../lib/rules/template-no-element-event-actions');
3+
4+
const ruleTester = new RuleTester({
5+
parser: require.resolve('ember-eslint-parser'),
6+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
7+
});
8+
9+
ruleTester.run('template-no-element-event-actions', rule, {
10+
valid: [
11+
{
12+
filename: 'my-component.gjs',
13+
code: `
14+
import Component from '@glimmer/component';
15+
export default class MyComponent extends Component {
16+
<template>
17+
<button {{on "click" this.handleClick}}>Click</button>
18+
</template>
19+
}
20+
`,
21+
output: null,
22+
},
23+
{
24+
filename: 'my-component.gjs',
25+
code: `
26+
import Component from '@glimmer/component';
27+
export default class MyComponent extends Component {
28+
<template>
29+
<div>No events</div>
30+
</template>
31+
}
32+
`,
33+
output: null,
34+
},
35+
],
36+
37+
invalid: [
38+
{
39+
filename: 'my-component.gjs',
40+
code: `
41+
import Component from '@glimmer/component';
42+
export default class MyComponent extends Component {
43+
<template>
44+
<button onclick={{this.handleClick}}>Click</button>
45+
</template>
46+
}
47+
`,
48+
output: null,
49+
errors: [
50+
{
51+
messageId: 'noElementEventActions',
52+
},
53+
],
54+
},
55+
{
56+
filename: 'my-component.gjs',
57+
code: `
58+
import Component from '@glimmer/component';
59+
export default class MyComponent extends Component {
60+
<template>
61+
<div onmouseenter={{this.handleHover}}>Hover</div>
62+
</template>
63+
}
64+
`,
65+
output: null,
66+
errors: [
67+
{
68+
messageId: 'noElementEventActions',
69+
},
70+
],
71+
},
72+
],
73+
});

0 commit comments

Comments
 (0)