Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add prefer-event-target rule #1792

Merged
merged 14 commits into from May 8, 2022
1 change: 1 addition & 0 deletions configs/recommended.js
Expand Up @@ -79,6 +79,7 @@ module.exports = {
'unicorn/prefer-dom-node-dataset': 'error',
'unicorn/prefer-dom-node-remove': 'error',
'unicorn/prefer-dom-node-text-content': 'error',
'unicorn/prefer-event-target': 'error',
'unicorn/prefer-export-from': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-json-parse-buffer': 'off',
Expand Down
34 changes: 34 additions & 0 deletions docs/rules/prefer-event-target.md
@@ -0,0 +1,34 @@
# Prefer `EventTarget` over `EventEmitter`

<!-- Do not manually modify RULE_NOTICE part. Run: `npm run generate-rule-notices` -->
<!-- RULE_NOTICE -->
*This rule is part of the [recommended](https://github.com/sindresorhus/eslint-plugin-unicorn#recommended-config) config.*
<!-- /RULE_NOTICE -->

While [`EventEmitter`](https://nodejs.org/api/events.html#class-eventemitter) is only available in Node.js, [`EventTarget`](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget) is also available in *Deno* and browsers.

This rule reduces the bundle size and makes your code more cross-platform friendly.

See the [differences](https://nodejs.org/api/events.html#eventtarget-and-event-api) between `EventEmitter` and `EventTarget`.

## Fail

```js
import {EventEmitter} from 'node:event';

class Foo extends EventEmitter {}
```

```js
const emitter = new EventEmitter();
```

## Pass

```js
class Foo extends EventTarget {}
```

```js
const target = new EventTarget();
```
1 change: 1 addition & 0 deletions readme.md
Expand Up @@ -119,6 +119,7 @@ Each rule has emojis denoting:
| [prefer-dom-node-dataset](docs/rules/prefer-dom-node-dataset.md) | Prefer using `.dataset` on DOM elements over calling attribute methods. | ✅ | 🔧 | |
| [prefer-dom-node-remove](docs/rules/prefer-dom-node-remove.md) | Prefer `childNode.remove()` over `parentNode.removeChild(childNode)`. | ✅ | 🔧 | 💡 |
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 |
| [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | ✅ | | |
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | 💡 |
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | |
Expand Down
39 changes: 39 additions & 0 deletions rules/prefer-event-target.js
@@ -0,0 +1,39 @@
'use strict';
const {matches} = require('./selectors/index.js');

const MESSAGE_ID = 'prefer-event-target';
const messages = {
[MESSAGE_ID]: 'Prefer `EventTarget` over `EventEmitter`.',
};

const selector = [
'Identifier',
'[name="EventEmitter"]',
matches([
'ClassDeclaration > .superClass',
'ClassExpression > .superClass',
'NewExpression > .callee',
]),
jopemachine marked this conversation as resolved.
Show resolved Hide resolved
].join('');
jopemachine marked this conversation as resolved.
Show resolved Hide resolved

/** @param {import('eslint').Rule.RuleContext} context */
const create = () => ({
[selector](node) {
return {
node,
messageId: MESSAGE_ID,
};
},
});

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
create,
meta: {
type: 'suggestion',
docs: {
description: 'Prefer `EventTarget` over `EventEmitter`.',
},
messages,
},
};
48 changes: 48 additions & 0 deletions test/prefer-event-target.mjs
@@ -0,0 +1,48 @@
import outdent from 'outdent';
import {getTester} from './utils/test.mjs';

const {test} = getTester(import.meta);

test.snapshot({
valid: [
'class Foo {}',
'class Foo extends OtherClass {}',
'class Foo extends EventTarget {}',
'const Foo = class extends EventTarget {}',
'const Foo = class extends foo.EventTarget {}',
'const Foo = class extends foo.bar.EventTarget {}',
'class Foo extends foo.EventEmitter {}',
'class Foo extends foo.bar.EventEmitter {}',
'class EventEmitter extends Foo {}',
'const Foo = class EventEmitter extends Foo {}',
'new Foo(EventEmitter)',
'new foo.EventEmitter()',
],
jopemachine marked this conversation as resolved.
Show resolved Hide resolved
invalid: [
'class Foo extends EventEmitter {}',
'class Foo extends EventEmitter { someMethod() {} }',
'const Foo = class extends EventEmitter {}',
outdent`
class Foo extends EventEmitter {
addListener() {}
removeListener() {}
}
`,
],
fisker marked this conversation as resolved.
Show resolved Hide resolved
});

test.snapshot({
valid: [
'EventTarget()',
'new EventTarget',
'const target = new EventTarget;',
jopemachine marked this conversation as resolved.
Show resolved Hide resolved
'const target = EventTarget()',
'const target = new Foo(EventEmitter);',
'EventEmitter()',
'const emitter = EventEmitter()',
],
jopemachine marked this conversation as resolved.
Show resolved Hide resolved
invalid: [
'new EventEmitter',
'const emitter = new EventEmitter;',
],
});
71 changes: 71 additions & 0 deletions test/snapshots/prefer-event-target.mjs.md
@@ -0,0 +1,71 @@
# Snapshot report for `test/prefer-event-target.mjs`

The actual snapshot is saved in `prefer-event-target.mjs.snap`.

Generated by [AVA](https://avajs.dev).

## Invalid #1
1 | class Foo extends EventEmitter {}

> Error 1/1
`␊
> 1 | class Foo extends EventEmitter {}␊
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
`

## Invalid #2
1 | class Foo extends EventEmitter { someMethod() {} }

> Error 1/1
`␊
> 1 | class Foo extends EventEmitter { someMethod() {} }␊
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
`

## Invalid #3
1 | const Foo = class extends EventEmitter {}

> Error 1/1
`␊
> 1 | const Foo = class extends EventEmitter {}␊
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
`

## Invalid #4
1 | class Foo extends EventEmitter {
2 | addListener() {}
3 | removeListener() {}
4 | }

> Error 1/1
`␊
> 1 | class Foo extends EventEmitter {␊
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
2 | addListener() {}␊
3 | removeListener() {}␊
4 | }␊
`

## Invalid #1
1 | new EventEmitter

> Error 1/1
`␊
> 1 | new EventEmitter␊
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
`

## Invalid #2
1 | const emitter = new EventEmitter;

> Error 1/1
`␊
> 1 | const emitter = new EventEmitter;␊
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
`
Binary file added test/snapshots/prefer-event-target.mjs.snap
Binary file not shown.