Skip to content

Commit 1629ebe

Browse files
authored
prefer-event-target: Ignore EventEmitter from @angular/core and eventemitter3 (#2197)
1 parent fa431ff commit 1629ebe

6 files changed

+141
-17
lines changed

rules/prefer-event-target.js

+77-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,82 @@
11
'use strict';
2+
const {findVariable} = require('@eslint-community/eslint-utils');
3+
const {getAncestor} = require('./utils/index.js');
4+
const {isStaticRequire, isStringLiteral, isMemberExpression} = require('./ast/index.js');
25

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

11+
const packagesShouldBeIgnored = new Set([
12+
'@angular/core',
13+
'eventemitter3',
14+
]);
15+
16+
const isConstVariableDeclarationId = node =>
17+
node.parent.type === 'VariableDeclarator'
18+
&& node.parent.id === node
19+
&& node.parent.parent.type === 'VariableDeclaration'
20+
&& node.parent.parent.kind === 'const'
21+
&& node.parent.parent.declarations.includes(node.parent);
22+
23+
function isAwaitImportOrRequireFromIgnoredPackages(node) {
24+
if (!node) {
25+
return false;
26+
}
27+
28+
let source;
29+
if (isStaticRequire(node)) {
30+
[source] = node.arguments;
31+
} else if (node.type === 'AwaitExpression' && node.argument.type === 'ImportExpression') {
32+
({source} = node.argument);
33+
}
34+
35+
if (isStringLiteral(source) && packagesShouldBeIgnored.has(source.value)) {
36+
return true;
37+
}
38+
39+
return false;
40+
}
41+
42+
function isFromIgnoredPackage(node) {
43+
if (!node) {
44+
return false;
45+
}
46+
47+
const importDeclaration = getAncestor(node, 'ImportDeclaration');
48+
if (packagesShouldBeIgnored.has(importDeclaration?.source.value)) {
49+
return true;
50+
}
51+
52+
// `const {EventEmitter} = ...`
53+
if (
54+
node.parent.type === 'Property'
55+
&& node.parent.value === node
56+
&& node.parent.key.type === 'Identifier'
57+
&& node.parent.key.name === 'EventEmitter'
58+
&& node.parent.parent.type === 'ObjectPattern'
59+
&& node.parent.parent.properties.includes(node.parent)
60+
&& isConstVariableDeclarationId(node.parent.parent)
61+
&& isAwaitImportOrRequireFromIgnoredPackages(node.parent.parent.parent.init)
62+
) {
63+
return true;
64+
}
65+
66+
// `const EventEmitter = (...).EventEmitter`
67+
if (
68+
isConstVariableDeclarationId(node)
69+
&& isMemberExpression(node.parent.init, {property: 'EventEmitter', optional: false, computed: false})
70+
&& isAwaitImportOrRequireFromIgnoredPackages(node.parent.init.object)
71+
) {
72+
return true;
73+
}
74+
75+
return false;
76+
}
77+
878
/** @param {import('eslint').Rule.RuleContext} context */
9-
const create = () => ({
79+
const create = context => ({
1080
Identifier(node) {
1181
if (!(
1282
node.name === 'EventEmitter'
@@ -21,6 +91,12 @@ const create = () => ({
2191
return;
2292
}
2393

94+
const scope = context.sourceCode.getScope(node);
95+
const variableNode = findVariable(scope, node)?.defs[0]?.name;
96+
if (isFromIgnoredPackage(variableNode)) {
97+
return;
98+
}
99+
24100
return {
25101
node,
26102
messageId: MESSAGE_ID,

rules/utils/get-ancestor.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
// TODO: Support more types
4+
function getPredicate(options) {
5+
if (typeof options === 'string') {
6+
return node => node.type === options;
7+
}
8+
}
9+
10+
function getAncestor(node, options) {
11+
const predicate = getPredicate(options);
12+
13+
for (;node.parent; node = node.parent) {
14+
if (predicate(node)) {
15+
return node;
16+
}
17+
}
18+
}
19+
20+
module.exports = getAncestor;

rules/utils/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,6 @@ module.exports = {
4848
shouldAddParenthesesToSpreadElementArgument: require('./should-add-parentheses-to-spread-element-argument.js'),
4949
singular: require('./singular.js'),
5050
toLocation: require('./to-location.js'),
51+
getAncestor: require('./get-ancestor.js'),
5152
};
5253

test/prefer-event-target.mjs

+21-14
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,24 @@ test.snapshot({
1717
'const Foo = class EventEmitter extends Foo {}',
1818
'new Foo(EventEmitter)',
1919
'new foo.EventEmitter()',
20+
...[
21+
'import {EventEmitter} from "@angular/core";',
22+
'const {EventEmitter} = require("@angular/core");',
23+
'const EventEmitter = require("@angular/core").EventEmitter;',
24+
'import {EventEmitter} from "eventemitter3";',
25+
'const {EventEmitter} = await import("eventemitter3");',
26+
'const EventEmitter = (await import("eventemitter3")).EventEmitter;',
27+
].map(code => outdent`
28+
${code}
29+
class Foo extends EventEmitter {}
30+
`),
31+
'EventTarget()',
32+
'new EventTarget',
33+
'const target = new EventTarget;',
34+
'const target = EventTarget()',
35+
'const target = new Foo(EventEmitter);',
36+
'EventEmitter()',
37+
'const emitter = EventEmitter()',
2038
],
2139
invalid: [
2240
'class Foo extends EventEmitter {}',
@@ -28,21 +46,10 @@ test.snapshot({
2846
removeListener() {}
2947
}
3048
`,
31-
],
32-
});
33-
34-
test.snapshot({
35-
valid: [
36-
'EventTarget()',
37-
'new EventTarget',
38-
'const target = new EventTarget;',
39-
'const target = EventTarget()',
40-
'const target = new Foo(EventEmitter);',
41-
'EventEmitter()',
42-
'const emitter = EventEmitter()',
43-
],
44-
invalid: [
4549
'new EventEmitter',
4650
'const emitter = new EventEmitter;',
51+
// For coverage
52+
'for (const {EventEmitter} of []) {new EventEmitter}',
53+
'for (const EventEmitter of []) {new EventEmitter}',
4754
],
4855
});

test/snapshots/prefer-event-target.mjs.md

+22-2
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Generated by [AVA](https://avajs.dev).
5050
4 | }␊
5151
`
5252

53-
## Invalid #1
53+
## Invalid #5
5454
1 | new EventEmitter
5555

5656
> Error 1/1
@@ -60,7 +60,7 @@ Generated by [AVA](https://avajs.dev).
6060
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
6161
`
6262

63-
## Invalid #2
63+
## Invalid #6
6464
1 | const emitter = new EventEmitter;
6565

6666
> Error 1/1
@@ -69,3 +69,23 @@ Generated by [AVA](https://avajs.dev).
6969
> 1 | const emitter = new EventEmitter;␊
7070
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
7171
`
72+
73+
## Invalid #7
74+
1 | for (const {EventEmitter} of []) {new EventEmitter}
75+
76+
> Error 1/1
77+
78+
`␊
79+
> 1 | for (const {EventEmitter} of []) {new EventEmitter}␊
80+
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
81+
`
82+
83+
## Invalid #8
84+
1 | for (const EventEmitter of []) {new EventEmitter}
85+
86+
> Error 1/1
87+
88+
`␊
89+
> 1 | for (const EventEmitter of []) {new EventEmitter}␊
90+
| ^^^^^^^^^^^^ Prefer \`EventTarget\` over \`EventEmitter\`.␊
91+
`
89 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)