Skip to content

Commit c24afd0

Browse files
keithamuskoddsson
andcommitted
feat: Add no-useless-passive rule
Co-authored-by: Kristján Oddsson <[email protected]>
1 parent d186731 commit c24afd0

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

docs/rules/no-useless-passive.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# No Useless Passive
2+
3+
This rule disallows setting `passive: true` for events on which it will have no effect.
4+
5+
Events where `passive: true` has an effect are: `touchstart`, `touchmove`, `wheel`, and `mousewheel`.
6+
7+
```js
8+
// bad (passive has no effect here)
9+
window.addEventListener('scroll', () => { /* ... */ }, { passive: true })
10+
11+
// good
12+
window.addEventListener('scroll', () => { /* ... */ })
13+
```
14+
15+
## Why?
16+
17+
Adding `passive: true` informs the browser that this event will not be calling `preventDefault` and as such is safe to asynchronously dispatch, freeing up the main thread for lag-free operation. However many events are not cancel-able and as such setting `passive: true` will have no effect on the browser.
18+
19+
It is safe to leave the option set, but this may have a negative effect on code authors, as they might believe setting `passive: true` has a positive effect on their operations, leading to a false-confidence in code with `passive: true`. As such, removing the option where it has no effect demonstrates to the code author that this code will need to avoid expensive operations as this might have a detrimental affect on UI performance.
20+
21+
## See Also
22+
23+
https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Improving_scrolling_performance_with_passive_listeners

lib/rules/no-useless-passive.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const passiveEventListenerNames = new Set(['touchstart', 'touchmove', 'wheel', 'mousewheel'])
2+
3+
const propIsPassiveTrue = prop => prop.key && prop.key.name === 'passive' && prop.value && prop.value.value === true
4+
5+
module.exports = {
6+
meta: {
7+
docs: {},
8+
fixable: 'code'
9+
},
10+
11+
create(context) {
12+
return {
13+
['CallExpression[callee.property.name="addEventListener"]']: function(node) {
14+
const [name, listener, options] = node.arguments
15+
if (name.type !== 'Literal') return
16+
if (passiveEventListenerNames.has(name.value)) return
17+
if (options && options.type === 'ObjectExpression') {
18+
const i = options.properties.findIndex(propIsPassiveTrue)
19+
if (i === -1) return
20+
const passiveProp = options.properties[i]
21+
const l = options.properties.length
22+
const source = context.getSourceCode()
23+
context.report({
24+
node: passiveProp,
25+
message: `"${name.value}" event listener is not cancellable and so \`passive: true\` does nothing.`,
26+
fix(fixer) {
27+
const removals = []
28+
if (l === 1) {
29+
removals.push(options)
30+
removals.push(...source.getTokensBetween(listener, options))
31+
} else {
32+
removals.push(passiveProp)
33+
if (i > 0) {
34+
removals.push(...source.getTokensBetween(options.properties[i - 1], passiveProp))
35+
} else {
36+
removals.push(...source.getTokensBetween(passiveProp, options.properties[i + 1]))
37+
}
38+
}
39+
return removals.map(t => fixer.remove(t))
40+
}
41+
})
42+
}
43+
}
44+
}
45+
}
46+
}

tests/no-useless-passive.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const rule = require('../lib/rules/no-useless-passive')
2+
const RuleTester = require('eslint').RuleTester
3+
4+
const ruleTester = new RuleTester()
5+
6+
ruleTester.run('no-useless-passive', rule, {
7+
valid: [
8+
{
9+
code: 'document.addEventListener("scroll", function(event) {})'
10+
},
11+
{
12+
code: 'document.addEventListener("resize", function(event) {})'
13+
},
14+
{
15+
code: 'document.addEventListener("resize", function(event) {}, { passive: false })'
16+
}
17+
],
18+
invalid: [
19+
{
20+
code: 'document.addEventListener("scroll", function(event) {}, { passive: true })',
21+
output: 'document.addEventListener("scroll", function(event) {} )',
22+
errors: [
23+
{
24+
message: '"scroll" event listener is not cancellable and so `passive: true` does nothing.',
25+
type: 'Property'
26+
}
27+
]
28+
},
29+
{
30+
code: 'document.addEventListener("scroll", function(event) {}, { passive: true, foo: 1 })',
31+
output: 'document.addEventListener("scroll", function(event) {}, { foo: 1 })',
32+
errors: [
33+
{
34+
message: '"scroll" event listener is not cancellable and so `passive: true` does nothing.',
35+
type: 'Property'
36+
}
37+
]
38+
}
39+
]
40+
})

0 commit comments

Comments
 (0)