225 lines
5.2 KiB
JavaScript
225 lines
5.2 KiB
JavaScript
|
'use strict';
|
||
|
const {getStaticValue} = require('@eslint-community/eslint-utils');
|
||
|
const {isNumberLiteral} = require('../ast/index.js');
|
||
|
|
||
|
const isStaticProperties = (node, object, properties) =>
|
||
|
node.type === 'MemberExpression'
|
||
|
&& !node.computed
|
||
|
&& !node.optional
|
||
|
&& node.object.type === 'Identifier'
|
||
|
&& node.object.name === object
|
||
|
&& node.property.type === 'Identifier'
|
||
|
&& properties.has(node.property.name);
|
||
|
const isFunctionCall = (node, functionName) => node.type === 'CallExpression'
|
||
|
&& !node.optional
|
||
|
&& node.callee.type === 'Identifier'
|
||
|
&& node.callee.name === functionName;
|
||
|
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math#static_properties
|
||
|
const mathProperties = new Set([
|
||
|
'E',
|
||
|
'LN2',
|
||
|
'LN10',
|
||
|
'LOG2E',
|
||
|
'LOG10E',
|
||
|
'PI',
|
||
|
'SQRT1_2',
|
||
|
'SQRT2',
|
||
|
]);
|
||
|
|
||
|
// `Math.{E,LN2,LN10,LOG2E,LOG10E,PI,SQRT1_2,SQRT2}`
|
||
|
const isMathProperty = node => isStaticProperties(node, 'Math', mathProperties);
|
||
|
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math#static_methods
|
||
|
const mathMethods = new Set([
|
||
|
'abs',
|
||
|
'acos',
|
||
|
'acosh',
|
||
|
'asin',
|
||
|
'asinh',
|
||
|
'atan',
|
||
|
'atanh',
|
||
|
'atan2',
|
||
|
'cbrt',
|
||
|
'ceil',
|
||
|
'clz32',
|
||
|
'cos',
|
||
|
'cosh',
|
||
|
'exp',
|
||
|
'expm1',
|
||
|
'floor',
|
||
|
'fround',
|
||
|
'hypot',
|
||
|
'imul',
|
||
|
'log',
|
||
|
'log1p',
|
||
|
'log10',
|
||
|
'log2',
|
||
|
'max',
|
||
|
'min',
|
||
|
'pow',
|
||
|
'random',
|
||
|
'round',
|
||
|
'sign',
|
||
|
'sin',
|
||
|
'sinh',
|
||
|
'sqrt',
|
||
|
'tan',
|
||
|
'tanh',
|
||
|
'trunc',
|
||
|
]);
|
||
|
// `Math.{abs, …, trunc}(…)`
|
||
|
const isMathMethodCall = node =>
|
||
|
node.type === 'CallExpression'
|
||
|
&& !node.optional
|
||
|
&& isStaticProperties(node.callee, 'Math', mathMethods);
|
||
|
|
||
|
// `Number(…)`
|
||
|
const isNumberCall = node => isFunctionCall(node, 'Number');
|
||
|
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#static_properties
|
||
|
const numberProperties = new Set([
|
||
|
'EPSILON',
|
||
|
'MAX_SAFE_INTEGER',
|
||
|
'MAX_VALUE',
|
||
|
'MIN_SAFE_INTEGER',
|
||
|
'MIN_VALUE',
|
||
|
'NaN',
|
||
|
'NEGATIVE_INFINITY',
|
||
|
'POSITIVE_INFINITY',
|
||
|
]);
|
||
|
const isNumberProperty = node => isStaticProperties(node, 'Number', numberProperties);
|
||
|
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#static_methods
|
||
|
const numberMethods = new Set([
|
||
|
'parseFloat',
|
||
|
'parseInt',
|
||
|
]);
|
||
|
const isNumberMethodCall = node =>
|
||
|
node.type === 'CallExpression'
|
||
|
&& !node.optional
|
||
|
&& isStaticProperties(node.callee, 'Number', numberMethods);
|
||
|
const isGlobalParseToNumberFunctionCall = node => isFunctionCall(node, 'parseInt') || isFunctionCall(node, 'parseFloat');
|
||
|
|
||
|
const isStaticNumber = (node, scope) =>
|
||
|
typeof getStaticValue(node, scope)?.value === 'number';
|
||
|
|
||
|
const isLengthProperty = node =>
|
||
|
node.type === 'MemberExpression'
|
||
|
&& !node.computed
|
||
|
&& !node.optional
|
||
|
&& node.property.type === 'Identifier'
|
||
|
&& node.property.name === 'length';
|
||
|
|
||
|
// `+` and `>>>` operators are handled separately
|
||
|
const mathOperators = new Set(['-', '*', '/', '%', '**', '<<', '>>', '|', '^', '&']);
|
||
|
function isNumber(node, scope) {
|
||
|
if (
|
||
|
isNumberLiteral(node)
|
||
|
|| isMathProperty(node)
|
||
|
|| isMathMethodCall(node)
|
||
|
|| isNumberCall(node)
|
||
|
|| isNumberProperty(node)
|
||
|
|| isNumberMethodCall(node)
|
||
|
|| isGlobalParseToNumberFunctionCall(node)
|
||
|
|| isLengthProperty(node)
|
||
|
) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
switch (node.type) {
|
||
|
case 'AssignmentExpression': {
|
||
|
const {operator} = node;
|
||
|
if (operator === '=' && isNumber(node.right, scope)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Fall through
|
||
|
}
|
||
|
|
||
|
case 'BinaryExpression': {
|
||
|
let {operator} = node;
|
||
|
|
||
|
if (node.type === 'AssignmentExpression') {
|
||
|
operator = operator.slice(0, -1);
|
||
|
}
|
||
|
|
||
|
if (operator === '+' && isNumber(node.left, scope) && isNumber(node.right, scope)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// `>>>` (zero-fill right shift) can't use on `BigInt`
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators
|
||
|
if (operator === '>>>') {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// `a * b` can be `BigInt`, we need make sure at least one side is number
|
||
|
if (mathOperators.has(operator) && (isNumber(node.left, scope) || isNumber(node.right, scope))) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 'UnaryExpression': {
|
||
|
const {operator} = node;
|
||
|
|
||
|
// `+` can't use on `BigInt`
|
||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators
|
||
|
if (operator === '+') {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if ((operator === '-' || operator === '~') && isNumber(node.argument, scope)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 'UpdateExpression': {
|
||
|
if (isNumber(node.argument, scope)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 'ConditionalExpression': {
|
||
|
const isConsequentNumber = isNumber(node.consequent, scope);
|
||
|
const isAlternateNumber = isNumber(node.alternate, scope);
|
||
|
|
||
|
if (isConsequentNumber && isAlternateNumber) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
const testStaticValueResult = getStaticValue(node.test, scope);
|
||
|
if (
|
||
|
testStaticValueResult !== null
|
||
|
&& (
|
||
|
(testStaticValueResult.value && isConsequentNumber)
|
||
|
|| (!testStaticValueResult.value && isAlternateNumber)
|
||
|
)
|
||
|
) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case 'SequenceExpression': {
|
||
|
if (isNumber(node.expressions[node.expressions.length - 1], scope)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
// No default
|
||
|
}
|
||
|
|
||
|
return isStaticNumber(node, scope);
|
||
|
}
|
||
|
|
||
|
module.exports = isNumber;
|