123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224 |
- '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;
|