Skip to content

Commit aee56fd

Browse files
authored
new-for-builtins: Refactor to use ReferenceTracker (#1829)
1 parent c9a572d commit aee56fd

12 files changed

+340
-75
lines changed

rules/new-for-builtins.js

Lines changed: 47 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use strict';
2+
const {ReferenceTracker} = require('eslint-utils');
23
const builtins = require('./utils/builtins.js');
3-
const isShadowed = require('./utils/is-shadowed.js');
4-
const {callExpressionSelector, newExpressionSelector} = require('./selectors/index.js');
54
const {
65
switchCallExpressionToNewExpression,
76
switchNewExpressionToCallExpression,
@@ -12,60 +11,64 @@ const messages = {
1211
disallow: 'Use `{{name}}()` instead of `new {{name}}()`.',
1312
};
1413

15-
/** @param {import('eslint').Rule.RuleContext} context */
16-
const create = context => {
17-
const sourceCode = context.getSourceCode();
18-
19-
return {
20-
[callExpressionSelector(builtins.enforceNew)](node) {
21-
const {callee, parent} = node;
22-
if (isShadowed(context.getScope(), callee)) {
23-
return;
24-
}
25-
26-
const {name} = callee;
14+
function * enforceNewExpression({sourceCode, tracker}) {
15+
const traceMap = Object.fromEntries(
16+
builtins.enforceNew.map(name => [name, {[ReferenceTracker.CALL]: true}]),
17+
);
2718

19+
for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
20+
if (name === 'Object') {
21+
const {parent} = node;
2822
if (
29-
name === 'Object'
30-
&& parent
31-
&& parent.type === 'BinaryExpression'
23+
parent.type === 'BinaryExpression'
3224
&& (parent.operator === '===' || parent.operator === '!==')
3325
&& (parent.left === node || parent.right === node)
3426
) {
35-
return;
27+
continue;
3628
}
29+
}
3730

38-
return {
39-
node,
40-
messageId: 'enforce',
41-
data: {name},
42-
fix: fixer => switchCallExpressionToNewExpression(node, sourceCode, fixer),
43-
};
44-
},
45-
[newExpressionSelector(builtins.disallowNew)](node) {
46-
const {callee} = node;
31+
yield {
32+
node,
33+
messageId: 'enforce',
34+
data: {name},
35+
fix: fixer => switchCallExpressionToNewExpression(node, sourceCode, fixer),
36+
};
37+
}
38+
}
4739

48-
if (isShadowed(context.getScope(), callee)) {
49-
return;
50-
}
40+
function * enforceCallExpression({sourceCode, tracker}) {
41+
const traceMap = Object.fromEntries(
42+
builtins.disallowNew.map(name => [name, {[ReferenceTracker.CONSTRUCT]: true}]),
43+
);
5144

52-
const {name} = callee;
53-
const problem = {
54-
node,
55-
messageId: 'disallow',
56-
data: {name},
45+
for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
46+
const problem = {
47+
node,
48+
messageId: 'disallow',
49+
data: {name},
50+
};
51+
52+
if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
53+
problem.fix = function * (fixer) {
54+
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
5755
};
56+
}
5857

59-
if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
60-
problem.fix = function * (fixer) {
61-
yield * switchNewExpressionToCallExpression(node, sourceCode, fixer);
62-
};
63-
}
58+
yield problem;
59+
}
60+
}
6461

65-
return problem;
66-
},
67-
};
68-
};
62+
/** @param {import('eslint').Rule.RuleContext} context */
63+
const create = context => ({
64+
* 'Program:exit'() {
65+
const sourceCode = context.getSourceCode();
66+
const tracker = new ReferenceTracker(context.getScope());
67+
68+
yield * enforceNewExpression({sourceCode, tracker});
69+
yield * enforceCallExpression({sourceCode, tracker});
70+
},
71+
});
6972

7073
/** @type {import('eslint').Rule.RuleModule} */
7174
module.exports = {

test/new-for-builtins.mjs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,6 @@ const disallowNewError = builtin => ({
1414
});
1515

1616
test({
17-
testerOptions: {
18-
// Make sure globals don't effect shadowed check result
19-
globals: {
20-
Map: 'writable',
21-
Set: 'readonly',
22-
WeakMap: 'off',
23-
BigInt: 'writable',
24-
Boolean: 'readonly',
25-
Number: 'off',
26-
},
27-
},
2817
valid: [
2918
'const foo = new Object()',
3019
'const foo = new Array()',
@@ -115,6 +104,7 @@ test({
115104
'Foo();new Bar();',
116105
// Ignored
117106
'const isObject = v => Object(v) === v;',
107+
'const isObject = v => globalThis.Object(v) === v;',
118108
'(x) !== Object(x)',
119109
],
120110
invalid: [
@@ -323,7 +313,12 @@ test({
323313
});
324314

325315
test.snapshot({
326-
valid: [],
316+
valid: [
317+
{
318+
code: 'new Symbol("")',
319+
globals: {Symbol: 'off'},
320+
},
321+
],
327322
invalid: [
328323
'const object = (Object)();',
329324
'const symbol = new (Symbol)("");',
@@ -383,5 +378,41 @@ test.snapshot({
383378
return new /**/ Symbol;
384379
}
385380
`,
381+
// Trace
382+
'new globalThis.String()',
383+
'new global.String()',
384+
'new self.String()',
385+
'new window.String()',
386+
outdent`
387+
const {String} = globalThis;
388+
new String();
389+
`,
390+
outdent`
391+
const {String: RenamedString} = globalThis;
392+
new RenamedString();
393+
`,
394+
outdent`
395+
const RenamedString = globalThis.String;
396+
new RenamedString();
397+
`,
398+
'globalThis.Array()',
399+
'global.Array()',
400+
'self.Array()',
401+
'window.Array()',
402+
outdent`
403+
const {Array: RenamedArray} = globalThis;
404+
RenamedArray();
405+
`,
406+
{
407+
code: 'globalThis.Array()',
408+
globals: {Array: 'off'},
409+
},
410+
{
411+
code: outdent`
412+
const {Array} = globalThis;
413+
Array();
414+
`,
415+
globals: {Array: 'off'},
416+
},
386417
],
387418
});

test/prevent-abbreviations.mjs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ const tests = {
242242
invalid: [
243243
{
244244
code: 'let e',
245-
errors: createErrors('Please rename the variable `e`. Suggested names are: `error`, `event`. A more descriptive name will do too.'),
245+
errors: createErrors('Please rename the variable `e`. Suggested names are: `error`, `event_`. A more descriptive name will do too.'),
246246
},
247247
{
248248
code: 'let eCbOpts',
@@ -369,8 +369,8 @@ const tests = {
369369

370370
{
371371
code: 'let evt',
372-
output: 'let event',
373-
errors: createErrors('The variable `evt` should be named `event`. A more descriptive name will do too.'),
372+
output: 'let event_',
373+
errors: createErrors('The variable `evt` should be named `event_`. A more descriptive name will do too.'),
374374
},
375375
{
376376
code: '({evt: 1})',
@@ -767,7 +767,7 @@ const tests = {
767767
},
768768
{
769769
code: 'class Res {}',
770-
errors: createErrors('Please rename the variable `Res`. Suggested names are: `Response`, `Result`. A more descriptive name will do too.'),
770+
errors: createErrors('Please rename the variable `Res`. Suggested names are: `Response_`, `Result`. A more descriptive name will do too.'),
771771
},
772772
{
773773
code: 'const Err = 1;',
@@ -792,7 +792,7 @@ const tests = {
792792

793793
{
794794
code: 'let doc',
795-
output: 'let document',
795+
output: 'let document_',
796796
errors: createErrors(),
797797
},
798798

@@ -1817,8 +1817,8 @@ test.typescript({
18171817
}
18181818
`,
18191819
output: outdent`
1820-
function onKeyDown(event: KeyboardEvent) {
1821-
if (event.keyCode) {}
1820+
function onKeyDown(event_: KeyboardEvent) {
1821+
if (event_.keyCode) {}
18221822
}
18231823
`,
18241824
options: [

0 commit comments

Comments
 (0)