Skip to content

Commit 347abff

Browse files
authored
Add prefer-add-event-listener-options rule (#3169)
1 parent 2c19771 commit 347abff

7 files changed

Lines changed: 320 additions & 0 deletions
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# prefer-add-event-listener-options
2+
3+
📝 Prefer an options object over a boolean in `.addEventListener()`.
4+
5+
💼 This rule is enabled in the following [configs](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config): ✅ `recommended`, ☑️ `unopinionated`.
6+
7+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
8+
9+
<!-- end auto-generated rule header -->
10+
<!-- Do not manually modify this header. Run: `npm run fix:eslint-docs` -->
11+
12+
Prefer the options object form of `.addEventListener()` over the legacy boolean `useCapture` argument.
13+
14+
The object form makes the capture behavior explicit and leaves room for other listener options like `passive`, `once`, and `signal`.
15+
16+
## Examples
17+
18+
```js
19+
//
20+
element.addEventListener('click', listener, true);
21+
22+
//
23+
element.addEventListener('click', listener, {capture: true});
24+
```
25+
26+
```js
27+
//
28+
element.addEventListener('click', listener, false);
29+
30+
//
31+
element.addEventListener('click', listener, {capture: false});
32+
```

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export default [
180180
| [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | ✅ ☑️ | 🔧 | |
181181
| [numeric-separators-style](docs/rules/numeric-separators-style.md) | Enforce the style of numeric separators by correctly grouping digits. | ✅ ☑️ | 🔧 | |
182182
| [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) | Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. | ✅ ☑️ | 🔧 | |
183+
| [prefer-add-event-listener-options](docs/rules/prefer-add-event-listener-options.md) | Prefer an options object over a boolean in `.addEventListener()`. | ✅ ☑️ | 🔧 | |
183184
| [prefer-array-find](docs/rules/prefer-array-find.md) | Prefer `.find(…)` and `.findLast(…)` over the first or last element from `.filter(…)`. | ✅ ☑️ | 🔧 | 💡 |
184185
| [prefer-array-flat](docs/rules/prefer-array-flat.md) | Prefer `Array#flat()` over legacy techniques to flatten arrays. | ✅ ☑️ | 🔧 | |
185186
| [prefer-array-flat-map](docs/rules/prefer-array-flat-map.md) | Prefer `.flatMap(…)` over `.map(…).flat()`. | ✅ ☑️ | 🔧 | |

rules/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export {default as 'no-useless-undefined'} from './no-useless-undefined.js';
118118
export {default as 'no-zero-fractions'} from './no-zero-fractions.js';
119119
export {default as 'number-literal-case'} from './number-literal-case.js';
120120
export {default as 'numeric-separators-style'} from './numeric-separators-style.js';
121+
export {default as 'prefer-add-event-listener-options'} from './prefer-add-event-listener-options.js';
121122
export {default as 'prefer-add-event-listener'} from './prefer-add-event-listener.js';
122123
export {default as 'prefer-array-find'} from './prefer-array-find.js';
123124
export {default as 'prefer-array-flat-map'} from './prefer-array-flat-map.js';
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {isMethodCall} from './ast/index.js';
2+
3+
const MESSAGE_ID = 'prefer-add-event-listener-options';
4+
const messages = {
5+
[MESSAGE_ID]: 'Prefer `{{replacement}}` over `{{value}}`.',
6+
};
7+
8+
const isBooleanLiteral = node =>
9+
node?.type === 'Literal'
10+
&& typeof node.value === 'boolean';
11+
12+
const create = context => {
13+
context.on('CallExpression', callExpression => {
14+
if (!isMethodCall(callExpression, {
15+
method: 'addEventListener',
16+
argumentsLength: 3,
17+
optionalCall: false,
18+
optionalMember: false,
19+
})) {
20+
return;
21+
}
22+
23+
const optionsNode = callExpression.arguments[2];
24+
if (!isBooleanLiteral(optionsNode)) {
25+
return;
26+
}
27+
28+
const replacement = `{capture: ${optionsNode.value}}`;
29+
return {
30+
node: optionsNode,
31+
messageId: MESSAGE_ID,
32+
data: {
33+
value: String(optionsNode.value),
34+
replacement,
35+
},
36+
fix: fixer => fixer.replaceText(optionsNode, replacement),
37+
};
38+
});
39+
};
40+
41+
/** @type {import('eslint').Rule.RuleModule} */
42+
const config = {
43+
create,
44+
meta: {
45+
type: 'suggestion',
46+
docs: {
47+
description: 'Prefer an options object over a boolean in `.addEventListener()`.',
48+
recommended: 'unopinionated',
49+
},
50+
fixable: 'code',
51+
messages,
52+
languages: [
53+
'js/js',
54+
],
55+
},
56+
};
57+
58+
export default config;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import outdent from 'outdent';
2+
import {getTester} from './utils/test.js';
3+
4+
const {test} = getTester(import.meta);
5+
6+
test.snapshot({
7+
valid: [
8+
'window.addEventListener("click", listener)',
9+
'window.addEventListener("click", listener, {capture: true})',
10+
'window.addEventListener("click", listener, {capture: false})',
11+
'window.addEventListener("click", listener, {passive: true})',
12+
'window.addEventListener("click", listener, {once: true})',
13+
'window.addEventListener("click", listener, {signal})',
14+
'window.addEventListener("click", listener, options)',
15+
'window.addEventListener("click", listener, capture)',
16+
'window.addEventListener("click", listener, Boolean(value))',
17+
'window.addEventListener("click", listener, condition ? true : false)',
18+
'window["addEventListener"]("click", listener, true)',
19+
'window?.addEventListener("click", listener, true)',
20+
'window.addEventListener?.("click", listener, true)',
21+
'window.addEventListener("click", ...arguments_, true)',
22+
],
23+
invalid: [
24+
'window.addEventListener("click", listener, true)',
25+
'window.addEventListener("click", listener, false)',
26+
'window.addEventListener("click", () => {}, true)',
27+
'window.addEventListener("click", function () {}, false)',
28+
'document.body.addEventListener("click", listener, true)',
29+
'(window).addEventListener("click", listener, false)',
30+
'window.addEventListener("click", listener, /* useCapture */ true)',
31+
'window.addEventListener("click", listener, true /* useCapture */)',
32+
outdent`
33+
window.addEventListener(
34+
"click",
35+
listener,
36+
true
37+
)
38+
`,
39+
],
40+
});
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
# Snapshot report for `test/prefer-add-event-listener-options.js`
2+
3+
The actual snapshot is saved in `prefer-add-event-listener-options.js.snap`.
4+
5+
Generated by [AVA](https://avajs.dev).
6+
7+
## invalid(1): window.addEventListener("click", listener, true)
8+
9+
> Input
10+
11+
`␊
12+
1 | window.addEventListener("click", listener, true)␊
13+
`
14+
15+
> Error 1/1
16+
17+
`␊
18+
Message:␊
19+
> 1 | window.addEventListener("click", listener, true)␊
20+
| ^^^^ Prefer \`{capture: true}\` over \`true\`.␊
21+
22+
Output:␊
23+
1 | window.addEventListener("click", listener, {capture: true})␊
24+
`
25+
26+
## invalid(2): window.addEventListener("click", listener, false)
27+
28+
> Input
29+
30+
`␊
31+
1 | window.addEventListener("click", listener, false)␊
32+
`
33+
34+
> Error 1/1
35+
36+
`␊
37+
Message:␊
38+
> 1 | window.addEventListener("click", listener, false)␊
39+
| ^^^^^ Prefer \`{capture: false}\` over \`false\`.␊
40+
41+
Output:␊
42+
1 | window.addEventListener("click", listener, {capture: false})␊
43+
`
44+
45+
## invalid(3): window.addEventListener("click", () => {}, true)
46+
47+
> Input
48+
49+
`␊
50+
1 | window.addEventListener("click", () => {}, true)␊
51+
`
52+
53+
> Error 1/1
54+
55+
`␊
56+
Message:␊
57+
> 1 | window.addEventListener("click", () => {}, true)␊
58+
| ^^^^ Prefer \`{capture: true}\` over \`true\`.␊
59+
60+
Output:␊
61+
1 | window.addEventListener("click", () => {}, {capture: true})␊
62+
`
63+
64+
## invalid(4): window.addEventListener("click", function () {}, false)
65+
66+
> Input
67+
68+
`␊
69+
1 | window.addEventListener("click", function () {}, false)␊
70+
`
71+
72+
> Error 1/1
73+
74+
`␊
75+
Message:␊
76+
> 1 | window.addEventListener("click", function () {}, false)␊
77+
| ^^^^^ Prefer \`{capture: false}\` over \`false\`.␊
78+
79+
Output:␊
80+
1 | window.addEventListener("click", function () {}, {capture: false})␊
81+
`
82+
83+
## invalid(5): document.body.addEventListener("click", listener, true)
84+
85+
> Input
86+
87+
`␊
88+
1 | document.body.addEventListener("click", listener, true)␊
89+
`
90+
91+
> Error 1/1
92+
93+
`␊
94+
Message:␊
95+
> 1 | document.body.addEventListener("click", listener, true)␊
96+
| ^^^^ Prefer \`{capture: true}\` over \`true\`.␊
97+
98+
Output:␊
99+
1 | document.body.addEventListener("click", listener, {capture: true})␊
100+
`
101+
102+
## invalid(6): (window).addEventListener("click", listener, false)
103+
104+
> Input
105+
106+
`␊
107+
1 | (window).addEventListener("click", listener, false)␊
108+
`
109+
110+
> Error 1/1
111+
112+
`␊
113+
Message:␊
114+
> 1 | (window).addEventListener("click", listener, false)␊
115+
| ^^^^^ Prefer \`{capture: false}\` over \`false\`.␊
116+
117+
Output:␊
118+
1 | (window).addEventListener("click", listener, {capture: false})␊
119+
`
120+
121+
## invalid(7): window.addEventListener("click", listener, /* useCapture */ true)
122+
123+
> Input
124+
125+
`␊
126+
1 | window.addEventListener("click", listener, /* useCapture */ true)␊
127+
`
128+
129+
> Error 1/1
130+
131+
`␊
132+
Message:␊
133+
> 1 | window.addEventListener("click", listener, /* useCapture */ true)␊
134+
| ^^^^ Prefer \`{capture: true}\` over \`true\`.␊
135+
136+
Output:␊
137+
1 | window.addEventListener("click", listener, /* useCapture */ {capture: true})␊
138+
`
139+
140+
## invalid(8): window.addEventListener("click", listener, true /* useCapture */)
141+
142+
> Input
143+
144+
`␊
145+
1 | window.addEventListener("click", listener, true /* useCapture */)␊
146+
`
147+
148+
> Error 1/1
149+
150+
`␊
151+
Message:␊
152+
> 1 | window.addEventListener("click", listener, true /* useCapture */)␊
153+
| ^^^^ Prefer \`{capture: true}\` over \`true\`.␊
154+
155+
Output:␊
156+
1 | window.addEventListener("click", listener, {capture: true} /* useCapture */)␊
157+
`
158+
159+
## invalid(9): window.addEventListener( "click", listener, true )
160+
161+
> Input
162+
163+
`␊
164+
1 | window.addEventListener(␊
165+
2 | "click",␊
166+
3 | listener,␊
167+
4 | true␊
168+
5 | )␊
169+
`
170+
171+
> Error 1/1
172+
173+
`␊
174+
Message:␊
175+
1 | window.addEventListener(␊
176+
2 | "click",␊
177+
3 | listener,␊
178+
> 4 | true␊
179+
| ^^^^ Prefer \`{capture: true}\` over \`true\`.␊
180+
5 | )␊
181+
182+
Output:␊
183+
1 | window.addEventListener(␊
184+
2 | "click",␊
185+
3 | listener,␊
186+
4 | {capture: true}␊
187+
5 | )␊
188+
`
681 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)