Skip to content

Commit f2ca555

Browse files
authored
Improve isNumber (#1653)
1 parent 9b04e43 commit f2ca555

File tree

5 files changed

+368
-45
lines changed

5 files changed

+368
-45
lines changed

rules/no-new-array.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function getProblem(context, node) {
5757

5858
const onlyElementText = `${maybeSemiColon}[${text}]`;
5959
const result = getStaticValue(argumentNode, context.getScope());
60-
if (result !== null) {
60+
if (result !== null && typeof result.value !== 'number') {
6161
problem.fix = fixer => fixer.replaceText(node, onlyElementText);
6262
return problem;
6363
}

rules/utils/is-number.js

Lines changed: 72 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,6 @@ const isNumberMethodCall = node =>
100100
&& isStaticProperties(node.callee, 'Number', numberMethods);
101101
const isGlobalParseToNumberFunctionCall = node => isFunctionCall(node, 'parseInt') || isFunctionCall(node, 'parseFloat');
102102

103-
// `+x`, `-x`
104-
const numberUnaryOperators = new Set(['-', '+', '~']);
105-
const isNumberUnaryExpression = node =>
106-
node.type === 'UnaryExpression'
107-
&& node.prefix
108-
&& numberUnaryOperators.has(node.operator);
109-
110103
const isStaticNumber = (node, scope) => {
111104
const staticResult = getStaticValue(node, scope);
112105
return staticResult !== null && typeof staticResult.value === 'number';
@@ -120,7 +113,8 @@ const isLengthProperty = node =>
120113
&& node.property.type === 'Identifier'
121114
&& node.property.name === 'length';
122115

123-
const mathOperators = new Set(['-', '*', '/', '%', '**', '<<', '>>', '>>>', '|', '^', '&']);
116+
// `+` and `>>>` operators are handled separately
117+
const mathOperators = new Set(['-', '*', '/', '%', '**', '<<', '>>', '|', '^', '&']);
124118
function isNumber(node, scope) {
125119
if (
126120
isNumberLiteral(node)
@@ -130,37 +124,97 @@ function isNumber(node, scope) {
130124
|| isNumberProperty(node)
131125
|| isNumberMethodCall(node)
132126
|| isGlobalParseToNumberFunctionCall(node)
133-
|| isNumberUnaryExpression(node)
134127
|| isLengthProperty(node)
135128
) {
136129
return true;
137130
}
138131

139132
switch (node.type) {
140-
case 'BinaryExpression':
141133
case 'AssignmentExpression': {
134+
const {operator} = node;
135+
if (operator === '=' && isNumber(node.right, scope)) {
136+
return true;
137+
}
138+
139+
// Fall through
140+
}
141+
142+
case 'BinaryExpression': {
142143
let {operator} = node;
143144

144145
if (node.type === 'AssignmentExpression') {
145146
operator = operator.slice(0, -1);
146147
}
147148

149+
if (operator === '+' && isNumber(node.left, scope) && isNumber(node.right, scope)) {
150+
return true;
151+
}
152+
153+
// `>>>` (zero-fill right shift) can't use on `BigInt`
154+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators
155+
if (operator === '>>>') {
156+
return true;
157+
}
158+
159+
// `a * b` can be `BigInt`, we need make sure at least one side is number
160+
if (mathOperators.has(operator) && (isNumber(node.left, scope) || isNumber(node.right, scope))) {
161+
return true;
162+
}
163+
164+
break;
165+
}
166+
167+
case 'UnaryExpression': {
168+
const {operator} = node;
169+
170+
// `+` can't use on `BigInt`
171+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators
148172
if (operator === '+') {
149-
return isNumber(node.left, scope) && isNumber(node.right, scope);
173+
return true;
150174
}
151175

152-
// `a + b` can be `BigInt`, we need make sure at least one side is number
153-
if (mathOperators.has(operator)) {
154-
return isNumber(node.left, scope) || isNumber(node.right, scope);
176+
if ((operator === '-' || operator === '~') && isNumber(node.argument, scope)) {
177+
return true;
155178
}
156179

157180
break;
158181
}
159182

160-
case 'ConditionalExpression':
161-
return isNumber(node.consequent, scope) && isNumber(node.alternate, scope);
162-
case 'SequenceExpression':
163-
return isNumber(node.expressions[node.expressions.length - 1], scope);
183+
case 'UpdateExpression':
184+
if (isNumber(node.argument, scope)) {
185+
return true;
186+
}
187+
188+
break;
189+
case 'ConditionalExpression': {
190+
const isConsequentNumber = isNumber(node.consequent, scope);
191+
const isAlternateNumber = isNumber(node.alternate, scope);
192+
193+
if (isConsequentNumber && isAlternateNumber) {
194+
return true;
195+
}
196+
197+
const testStaticValueResult = getStaticValue(node.test, scope);
198+
if (
199+
testStaticValueResult !== null
200+
&& (
201+
(testStaticValueResult.value && isConsequentNumber)
202+
|| (!testStaticValueResult.value && isAlternateNumber)
203+
)
204+
) {
205+
return true;
206+
}
207+
208+
break;
209+
}
210+
211+
case 'SequenceExpression': {
212+
if (isNumber(node.expressions[node.expressions.length - 1], scope)) {
213+
return true;
214+
}
215+
216+
break;
217+
}
164218
// No default
165219
}
166220

test/no-new-array.mjs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,23 @@ test.snapshot({
156156
'new Array(parseInt(foo))',
157157
'new Array(Number.parseInt(foo))',
158158
'new Array(+foo)',
159-
'new Array(-foo)',
160-
'new Array(~foo)',
159+
'new Array(-Math.PI)',
160+
'new Array(-"-2")',
161161
'new Array(foo.length)',
162162
'const foo = 1; new Array(foo + 2)',
163163
'new Array(foo - 2)',
164164
'new Array(foo -= 2)',
165165
'new Array(foo ? 1 : 2)',
166+
'const truthy = "truthy"; new Array(truthy ? 1 : foo)',
167+
'const falsy = !"truthy"; new Array(falsy ? foo : 1)',
166168
'new Array((1n, 2))',
167169
'new Array(Number.NaN)',
168170
'new Array(NaN)',
171+
'new Array(foo >>> bar)',
172+
'new Array(foo >>>= bar)',
173+
'new Array(++bar.length)',
174+
'new Array(bar.length++)',
175+
'new Array(foo = bar.length)',
169176
// Not number
170177
'new Array("0xff")',
171178
'new Array(Math.NON_EXISTS_PROPERTY)',
@@ -181,5 +188,13 @@ test.snapshot({
181188
'new Array(foo ||= 1)',
182189
'new Array(foo ? 1n : 2)',
183190
'new Array((1, 2n))',
191+
'new Array(-foo)',
192+
'new Array(~foo)',
193+
'new Array(typeof 1)',
194+
'const truthy = "truthy"; new Array(truthy ? foo : 1)',
195+
'const falsy = !"truthy"; new Array(falsy ? 1 : foo)',
196+
'new Array(unknown ? foo : 1)',
197+
'new Array(unknown ? 1 : foo)',
198+
'new Array(++foo)',
184199
],
185200
});

0 commit comments

Comments
 (0)