Skip to content

Commit 5ba0f83

Browse files
authored
Better fix for new-for-builtins (#1022)
1 parent 62a2f92 commit 5ba0f83

File tree

6 files changed

+123
-27
lines changed

6 files changed

+123
-27
lines changed

rules/new-for-builtins.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const getDocumentationUrl = require('./utils/get-documentation-url');
33
const builtins = require('./utils/builtins');
44
const isShadowed = require('./utils/is-shadowed');
5+
const isNewExpressionWithParentheses = require('./utils/is-new-expression-with-parentheses');
56

67
const messages = {
78
enforce: 'Use `new {{name}}()` instead of `{{name}}()`.',
@@ -12,6 +13,8 @@ const enforceNew = new Set(builtins.enforceNew);
1213
const disallowNew = new Set(builtins.disallowNew);
1314

1415
const create = context => {
16+
const sourceCode = context.getSourceCode();
17+
1518
return {
1619
CallExpression: node => {
1720
const {callee, parent} = node;
@@ -38,7 +41,7 @@ const create = context => {
3841
},
3942
NewExpression: node => {
4043
const {callee, range} = node;
41-
const {name, range: calleeRange} = callee;
44+
const {name} = callee;
4245

4346
if (disallowNew.has(name) && !isShadowed(context.getScope(), callee)) {
4447
const problem = {
@@ -48,10 +51,19 @@ const create = context => {
4851
};
4952

5053
if (name !== 'String' && name !== 'Boolean' && name !== 'Number') {
51-
problem.fix = fixer => fixer.removeRange([
52-
range[0],
53-
calleeRange[0]
54-
]);
54+
problem.fix = function * (fixer) {
55+
const [start] = range;
56+
let end = start + 3; // `3` = length of `new`
57+
const textAfter = sourceCode.text.slice(end);
58+
const [leadingSpaces] = textAfter.match(/^\s*/);
59+
end += leadingSpaces.length;
60+
61+
yield fixer.removeRange([start, end]);
62+
63+
if (!isNewExpressionWithParentheses(node, sourceCode)) {
64+
yield fixer.insertTextAfter(node, '()');
65+
}
66+
};
5567
}
5668

5769
context.report(problem);
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
'use strict';
2+
3+
const {isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
4+
5+
/**
6+
Determine if a constructor function is newed-up with parens.
7+
8+
@param {Node} node - The `NewExpression` node to be checked.
9+
@param {SourceCode} sourceCode - The source code object.
10+
@returns {boolean} True if the constructor is called with parens.
11+
12+
Copied from https://github.com/eslint/eslint/blob/cc4871369645c3409dc56ded7a555af8a9f63d51/lib/rules/no-extra-parens.js#L252
13+
*/
14+
function isNewExpressionWithParentheses(node, sourceCode) {
15+
if (node.arguments.length > 0) {
16+
return true;
17+
}
18+
19+
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
20+
// The expression should end with its own parens, for example, `new new Foo()` is not a new expression with parens.
21+
return isOpeningParenToken(penultimateToken) &&
22+
isClosingParenToken(lastToken) &&
23+
node.callee.range[1] < node.range[1];
24+
}
25+
26+
module.exports = isNewExpressionWithParentheses;

rules/utils/should-add-parentheses-to-member-expression-object.js

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const {isOpeningParenToken, isClosingParenToken} = require('eslint-utils');
3+
const isNewExpressionWithParentheses = require('./is-new-expression-with-parentheses');
44

55
// Determine whether this node is a decimal integer literal.
66
// Copied from https://github.com/eslint/eslint/blob/cc4871369645c3409dc56ded7a555af8a9f63d51/lib/rules/utils/ast-utils.js#L1237
@@ -10,27 +10,6 @@ const isDecimalInteger = node =>
1010
typeof node.value === 'number' &&
1111
DECIMAL_INTEGER_PATTERN.test(node.raw);
1212

13-
/**
14-
Determine if a constructor function is newed-up with parens.
15-
16-
@param {Node} node - The `NewExpression` node to be checked.
17-
@param {SourceCode} sourceCode - The source code object.
18-
@returns {boolean} True if the constructor is called with parens.
19-
20-
Copied from https://github.com/eslint/eslint/blob/cc4871369645c3409dc56ded7a555af8a9f63d51/lib/rules/no-extra-parens.js#L252
21-
*/
22-
function isNewExpressionWithParentheses(node, sourceCode) {
23-
if (node.arguments.length > 0) {
24-
return true;
25-
}
26-
27-
const [penultimateToken, lastToken] = sourceCode.getLastTokens(node, 2);
28-
// The expression should end with its own parens, for example, `new new Foo()` is not a new expression with parens.
29-
return isOpeningParenToken(penultimateToken) &&
30-
isClosingParenToken(lastToken) &&
31-
node.callee.range[1] < node.range[1];
32-
}
33-
3413
/**
3514
Check if parentheses should to be added to a `node` when it's used as an `object` of `MemberExpression`.
3615

test/new-for-builtins.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,13 @@ test({
321321
}
322322
]
323323
});
324+
325+
test.visualize({
326+
valid: [],
327+
invalid: [
328+
'const object = (Object)();',
329+
'const symbol = new (Symbol)("");',
330+
'const symbol = new /* comment */ Symbol("");',
331+
'const symbol = new Symbol;'
332+
]
333+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Snapshot report for `test/new-for-builtins.js`
2+
3+
The actual snapshot is saved in `new-for-builtins.js.snap`.
4+
5+
Generated by [AVA](https://avajs.dev).
6+
7+
## Invalid #1
8+
1 | const object = (Object)();
9+
10+
> Output
11+
12+
`␊
13+
1 | const object = new (Object)();␊
14+
`
15+
16+
> Error 1/1
17+
18+
`␊
19+
> 1 | const object = (Object)();␊
20+
| ^^^^^^^^^^ Use `new Object()` instead of `Object()`.␊
21+
`
22+
23+
## Invalid #2
24+
1 | const symbol = new (Symbol)("");
25+
26+
> Output
27+
28+
`␊
29+
1 | const symbol = (Symbol)("");␊
30+
`
31+
32+
> Error 1/1
33+
34+
`␊
35+
> 1 | const symbol = new (Symbol)("");␊
36+
| ^^^^^^^^^^^^^^^^ Use `Symbol()` instead of `new Symbol()`.␊
37+
`
38+
39+
## Invalid #3
40+
1 | const symbol = new /* comment */ Symbol("");
41+
42+
> Output
43+
44+
`␊
45+
1 | const symbol = /* comment */ Symbol("");␊
46+
`
47+
48+
> Error 1/1
49+
50+
`␊
51+
> 1 | const symbol = new /* comment */ Symbol("");␊
52+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Use `Symbol()` instead of `new Symbol()`.␊
53+
`
54+
55+
## Invalid #4
56+
1 | const symbol = new Symbol;
57+
58+
> Output
59+
60+
`␊
61+
1 | const symbol = Symbol();␊
62+
`
63+
64+
> Error 1/1
65+
66+
`␊
67+
> 1 | const symbol = new Symbol;␊
68+
| ^^^^^^^^^^ Use `Symbol()` instead of `new Symbol()`.␊
69+
`
347 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)