-
-
Notifications
You must be signed in to change notification settings - Fork 353
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
no-invalid-remove-event-listener
rule (#1216)
Co-authored-by: fisker Cheung <lionkay@gmail.com> Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
- Loading branch information
1 parent
68786b8
commit f0ff04d
Showing
7 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Prevent calling `EventTarget#removeEventListener()` with the result of an expression | ||
|
||
The [`removeEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/removeEventListener) function must be called with a reference to the same function that was passed to [`addEventListener`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). Calling `removeEventListener` with an inline function or the result of an inline `.bind()` call is indicative of an error, and won't actually remove the listener. | ||
|
||
## Fail | ||
|
||
```js | ||
window.removeEventListener('click', fn.bind(window)); | ||
``` | ||
|
||
```js | ||
window.removeEventListener('click', () => {}); | ||
``` | ||
|
||
```js | ||
window.removeEventListener('click', function () {}); | ||
``` | ||
|
||
```js | ||
class MyElement extends HTMLElement { | ||
handler() {} | ||
|
||
disconnectedCallback() { | ||
this.removeEventListener('click', this.handler.bind(this)); | ||
} | ||
} | ||
``` | ||
|
||
## Pass | ||
|
||
```js | ||
window.removeEventListener('click', listener); | ||
``` | ||
|
||
```js | ||
window.removeEventListener('click', getListener()); | ||
``` | ||
|
||
```js | ||
class MyElement extends HTMLElement { | ||
constructor() { | ||
super(); | ||
this.handler = this.handler.bind(this); | ||
} | ||
|
||
handler() {} | ||
|
||
disconnectedCallback() { | ||
this.removeEventListener('click', this.handler); | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
'use strict'; | ||
const {getFunctionHeadLocation} = require('eslint-utils'); | ||
const getDocumentationUrl = require('./utils/get-documentation-url.js'); | ||
const {methodCallSelector, matches} = require('./selectors/index.js'); | ||
|
||
const MESSAGE_ID = 'no-invalid-remove-event-listener'; | ||
const messages = { | ||
[MESSAGE_ID]: 'The listener argument should be a function reference.', | ||
}; | ||
|
||
const removeEventListenerSelector = [ | ||
methodCallSelector({ | ||
method: 'removeEventListener', | ||
minimumArguments: 2, | ||
}), | ||
'[arguments.0.type!="SpreadElement"]', | ||
matches([ | ||
'[arguments.1.type="FunctionExpression"]', | ||
'[arguments.1.type="ArrowFunctionExpression"]', | ||
methodCallSelector({method: 'bind', path: 'arguments.1'}), | ||
]), | ||
].join(''); | ||
|
||
/** @param {import('eslint').Rule.RuleContext} context */ | ||
const create = context => { | ||
return { | ||
[removeEventListenerSelector]: node => { | ||
const listener = node.arguments[1]; | ||
if (['ArrowFunctionExpression', 'FunctionExpression'].includes(listener.type)) { | ||
return { | ||
node: listener, | ||
loc: getFunctionHeadLocation(listener, context.getSourceCode()), | ||
messageId: MESSAGE_ID, | ||
}; | ||
} | ||
|
||
return { | ||
node: listener.callee.property, | ||
messageId: MESSAGE_ID, | ||
}; | ||
}, | ||
}; | ||
}; | ||
|
||
module.exports = { | ||
create, | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Prevent calling `EventTarget#removeEventListener()` with the result of an expression.', | ||
url: getDocumentationUrl(__filename), | ||
}, | ||
schema: [], | ||
messages, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import outdent from 'outdent'; | ||
import {getTester} from './utils/test.mjs'; | ||
|
||
const {test} = getTester(import.meta); | ||
|
||
test.snapshot({ | ||
valid: [ | ||
// CallExpression | ||
'new el.removeEventListener("click", () => {})', | ||
'el?.removeEventListener("click", () => {})', | ||
'el.removeEventListener?.("click", () => {})', | ||
'el.notRemoveEventListener("click", () => {})', | ||
'el[removeEventListener]("click", () => {})', | ||
|
||
// Arguments | ||
'el.removeEventListener("click")', | ||
'el.removeEventListener()', | ||
'el.removeEventListener(() => {})', | ||
'el.removeEventListener(...["click", () => {}], () => {})', | ||
'el.removeEventListener(() => {}, "click")', | ||
'window.removeEventListener("click", bind())', | ||
'window.removeEventListener("click", handler.notBind())', | ||
'window.removeEventListener("click", handler[bind]())', | ||
'window.removeEventListener("click", handler.bind?.())', | ||
'window.removeEventListener("click", handler?.bind())', | ||
|
||
'window.removeEventListener(handler)', | ||
outdent` | ||
class MyComponent { | ||
handler() {} | ||
disconnectedCallback() { | ||
this.removeEventListener('click', this.handler); | ||
} | ||
} | ||
`, | ||
'this.removeEventListener("click", getListener())', | ||
'el.removeEventListener("scroll", handler)', | ||
'el.removeEventListener("keydown", obj.listener)', | ||
'removeEventListener("keyup", () => {})', | ||
'removeEventListener("keydown", function () {})', | ||
|
||
], | ||
invalid: [ | ||
'window.removeEventListener("scroll", handler.bind(abc))', | ||
'window.removeEventListener("scroll", this.handler.bind(abc))', | ||
'window.removeEventListener("click", () => {})', | ||
'window.removeEventListener("keydown", function () {})', | ||
'el.removeEventListener("click", (e) => { e.preventDefault(); })', | ||
'el.removeEventListener("mouseover", fn.bind(abc))', | ||
'el.removeEventListener("mouseout", function (e) {})', | ||
'el.removeEventListener("mouseout", function (e) {}, true)', | ||
'el.removeEventListener("click", function (e) {}, ...moreArguments)', | ||
'el.removeEventListener(() => {}, () => {}, () => {})', | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# Snapshot report for `test/no-invalid-remove-event-listener.mjs` | ||
|
||
The actual snapshot is saved in `no-invalid-remove-event-listener.mjs.snap`. | ||
|
||
Generated by [AVA](https://avajs.dev). | ||
|
||
## Invalid #1 | ||
1 | window.removeEventListener("scroll", handler.bind(abc)) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | window.removeEventListener("scroll", handler.bind(abc))␊ | ||
| ^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #2 | ||
1 | window.removeEventListener("scroll", this.handler.bind(abc)) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | window.removeEventListener("scroll", this.handler.bind(abc))␊ | ||
| ^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #3 | ||
1 | window.removeEventListener("click", () => {}) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | window.removeEventListener("click", () => {})␊ | ||
| ^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #4 | ||
1 | window.removeEventListener("keydown", function () {}) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | window.removeEventListener("keydown", function () {})␊ | ||
| ^^^^^^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #5 | ||
1 | el.removeEventListener("click", (e) => { e.preventDefault(); }) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | el.removeEventListener("click", (e) => { e.preventDefault(); })␊ | ||
| ^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #6 | ||
1 | el.removeEventListener("mouseover", fn.bind(abc)) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | el.removeEventListener("mouseover", fn.bind(abc))␊ | ||
| ^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #7 | ||
1 | el.removeEventListener("mouseout", function (e) {}) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | el.removeEventListener("mouseout", function (e) {})␊ | ||
| ^^^^^^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #8 | ||
1 | el.removeEventListener("mouseout", function (e) {}, true) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | el.removeEventListener("mouseout", function (e) {}, true)␊ | ||
| ^^^^^^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #9 | ||
1 | el.removeEventListener("click", function (e) {}, ...moreArguments) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | el.removeEventListener("click", function (e) {}, ...moreArguments)␊ | ||
| ^^^^^^^^^ The listener argument should be a function reference.␊ | ||
` | ||
|
||
## Invalid #10 | ||
1 | el.removeEventListener(() => {}, () => {}, () => {}) | ||
|
||
> Error 1/1 | ||
`␊ | ||
> 1 | el.removeEventListener(() => {}, () => {}, () => {})␊ | ||
| ^^ The listener argument should be a function reference.␊ | ||
` |
Binary file not shown.