is-number.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. 'use strict';
  2. const {getStaticValue} = require('@eslint-community/eslint-utils');
  3. const {isNumberLiteral} = require('../ast/index.js');
  4. const isStaticProperties = (node, object, properties) =>
  5. node.type === 'MemberExpression'
  6. && !node.computed
  7. && !node.optional
  8. && node.object.type === 'Identifier'
  9. && node.object.name === object
  10. && node.property.type === 'Identifier'
  11. && properties.has(node.property.name);
  12. const isFunctionCall = (node, functionName) => node.type === 'CallExpression'
  13. && !node.optional
  14. && node.callee.type === 'Identifier'
  15. && node.callee.name === functionName;
  16. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math#static_properties
  17. const mathProperties = new Set([
  18. 'E',
  19. 'LN2',
  20. 'LN10',
  21. 'LOG2E',
  22. 'LOG10E',
  23. 'PI',
  24. 'SQRT1_2',
  25. 'SQRT2',
  26. ]);
  27. // `Math.{E,LN2,LN10,LOG2E,LOG10E,PI,SQRT1_2,SQRT2}`
  28. const isMathProperty = node => isStaticProperties(node, 'Math', mathProperties);
  29. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math#static_methods
  30. const mathMethods = new Set([
  31. 'abs',
  32. 'acos',
  33. 'acosh',
  34. 'asin',
  35. 'asinh',
  36. 'atan',
  37. 'atanh',
  38. 'atan2',
  39. 'cbrt',
  40. 'ceil',
  41. 'clz32',
  42. 'cos',
  43. 'cosh',
  44. 'exp',
  45. 'expm1',
  46. 'floor',
  47. 'fround',
  48. 'hypot',
  49. 'imul',
  50. 'log',
  51. 'log1p',
  52. 'log10',
  53. 'log2',
  54. 'max',
  55. 'min',
  56. 'pow',
  57. 'random',
  58. 'round',
  59. 'sign',
  60. 'sin',
  61. 'sinh',
  62. 'sqrt',
  63. 'tan',
  64. 'tanh',
  65. 'trunc',
  66. ]);
  67. // `Math.{abs, …, trunc}(…)`
  68. const isMathMethodCall = node =>
  69. node.type === 'CallExpression'
  70. && !node.optional
  71. && isStaticProperties(node.callee, 'Math', mathMethods);
  72. // `Number(…)`
  73. const isNumberCall = node => isFunctionCall(node, 'Number');
  74. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#static_properties
  75. const numberProperties = new Set([
  76. 'EPSILON',
  77. 'MAX_SAFE_INTEGER',
  78. 'MAX_VALUE',
  79. 'MIN_SAFE_INTEGER',
  80. 'MIN_VALUE',
  81. 'NaN',
  82. 'NEGATIVE_INFINITY',
  83. 'POSITIVE_INFINITY',
  84. ]);
  85. const isNumberProperty = node => isStaticProperties(node, 'Number', numberProperties);
  86. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number#static_methods
  87. const numberMethods = new Set([
  88. 'parseFloat',
  89. 'parseInt',
  90. ]);
  91. const isNumberMethodCall = node =>
  92. node.type === 'CallExpression'
  93. && !node.optional
  94. && isStaticProperties(node.callee, 'Number', numberMethods);
  95. const isGlobalParseToNumberFunctionCall = node => isFunctionCall(node, 'parseInt') || isFunctionCall(node, 'parseFloat');
  96. const isStaticNumber = (node, scope) =>
  97. typeof getStaticValue(node, scope)?.value === 'number';
  98. const isLengthProperty = node =>
  99. node.type === 'MemberExpression'
  100. && !node.computed
  101. && !node.optional
  102. && node.property.type === 'Identifier'
  103. && node.property.name === 'length';
  104. // `+` and `>>>` operators are handled separately
  105. const mathOperators = new Set(['-', '*', '/', '%', '**', '<<', '>>', '|', '^', '&']);
  106. function isNumber(node, scope) {
  107. if (
  108. isNumberLiteral(node)
  109. || isMathProperty(node)
  110. || isMathMethodCall(node)
  111. || isNumberCall(node)
  112. || isNumberProperty(node)
  113. || isNumberMethodCall(node)
  114. || isGlobalParseToNumberFunctionCall(node)
  115. || isLengthProperty(node)
  116. ) {
  117. return true;
  118. }
  119. switch (node.type) {
  120. case 'AssignmentExpression': {
  121. const {operator} = node;
  122. if (operator === '=' && isNumber(node.right, scope)) {
  123. return true;
  124. }
  125. // Fall through
  126. }
  127. case 'BinaryExpression': {
  128. let {operator} = node;
  129. if (node.type === 'AssignmentExpression') {
  130. operator = operator.slice(0, -1);
  131. }
  132. if (operator === '+' && isNumber(node.left, scope) && isNumber(node.right, scope)) {
  133. return true;
  134. }
  135. // `>>>` (zero-fill right shift) can't use on `BigInt`
  136. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators
  137. if (operator === '>>>') {
  138. return true;
  139. }
  140. // `a * b` can be `BigInt`, we need make sure at least one side is number
  141. if (mathOperators.has(operator) && (isNumber(node.left, scope) || isNumber(node.right, scope))) {
  142. return true;
  143. }
  144. break;
  145. }
  146. case 'UnaryExpression': {
  147. const {operator} = node;
  148. // `+` can't use on `BigInt`
  149. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#operators
  150. if (operator === '+') {
  151. return true;
  152. }
  153. if ((operator === '-' || operator === '~') && isNumber(node.argument, scope)) {
  154. return true;
  155. }
  156. break;
  157. }
  158. case 'UpdateExpression': {
  159. if (isNumber(node.argument, scope)) {
  160. return true;
  161. }
  162. break;
  163. }
  164. case 'ConditionalExpression': {
  165. const isConsequentNumber = isNumber(node.consequent, scope);
  166. const isAlternateNumber = isNumber(node.alternate, scope);
  167. if (isConsequentNumber && isAlternateNumber) {
  168. return true;
  169. }
  170. const testStaticValueResult = getStaticValue(node.test, scope);
  171. if (
  172. testStaticValueResult !== null
  173. && (
  174. (testStaticValueResult.value && isConsequentNumber)
  175. || (!testStaticValueResult.value && isAlternateNumber)
  176. )
  177. ) {
  178. return true;
  179. }
  180. break;
  181. }
  182. case 'SequenceExpression': {
  183. if (isNumber(node.expressions[node.expressions.length - 1], scope)) {
  184. return true;
  185. }
  186. break;
  187. }
  188. // No default
  189. }
  190. return isStaticNumber(node, scope);
  191. }
  192. module.exports = isNumber;