prefer-number-properties.js 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. 'use strict';
  2. const isShadowed = require('./utils/is-shadowed.js');
  3. const {
  4. referenceIdentifierSelector,
  5. callExpressionSelector,
  6. } = require('./selectors/index.js');
  7. const {replaceReferenceIdentifier} = require('./fix/index.js');
  8. const {fixSpaceAroundKeyword} = require('./fix/index.js');
  9. const METHOD_ERROR_MESSAGE_ID = 'method-error';
  10. const METHOD_SUGGESTION_MESSAGE_ID = 'method-suggestion';
  11. const PROPERTY_ERROR_MESSAGE_ID = 'property-error';
  12. const messages = {
  13. [METHOD_ERROR_MESSAGE_ID]: 'Prefer `Number.{{name}}()` over `{{name}}()`.',
  14. [METHOD_SUGGESTION_MESSAGE_ID]: 'Replace `{{name}}()` with `Number.{{name}}()`.',
  15. [PROPERTY_ERROR_MESSAGE_ID]: 'Prefer `Number.{{property}}` over `{{identifier}}`.',
  16. };
  17. const methods = {
  18. // Safe
  19. parseInt: true,
  20. parseFloat: true,
  21. // Unsafe
  22. isNaN: false,
  23. isFinite: false,
  24. };
  25. const methodsSelector = [
  26. callExpressionSelector(Object.keys(methods)),
  27. ' > ',
  28. '.callee',
  29. ].join('');
  30. const propertiesSelector = referenceIdentifierSelector(['NaN', 'Infinity']);
  31. const isNegative = node => {
  32. const {parent} = node;
  33. return parent && parent.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
  34. };
  35. /** @param {import('eslint').Rule.RuleContext} context */
  36. const create = context => {
  37. const sourceCode = context.getSourceCode();
  38. const options = {
  39. checkInfinity: true,
  40. ...context.options[0],
  41. };
  42. // Cache `NaN` and `Infinity` in `foo = {NaN, Infinity}`
  43. const reported = new WeakSet();
  44. return {
  45. [methodsSelector]: node => {
  46. if (isShadowed(context.getScope(), node)) {
  47. return;
  48. }
  49. const {name} = node;
  50. const isSafe = methods[name];
  51. const problem = {
  52. node,
  53. messageId: METHOD_ERROR_MESSAGE_ID,
  54. data: {
  55. name,
  56. },
  57. };
  58. const fix = fixer => replaceReferenceIdentifier(node, `Number.${name}`, fixer, sourceCode);
  59. if (isSafe) {
  60. problem.fix = fix;
  61. } else {
  62. problem.suggest = [
  63. {
  64. messageId: METHOD_SUGGESTION_MESSAGE_ID,
  65. data: {
  66. name,
  67. },
  68. fix,
  69. },
  70. ];
  71. }
  72. return problem;
  73. },
  74. [propertiesSelector]: node => {
  75. if (reported.has(node) || isShadowed(context.getScope(), node)) {
  76. return;
  77. }
  78. const {name, parent} = node;
  79. if (name === 'Infinity' && !options.checkInfinity) {
  80. return;
  81. }
  82. let property = name;
  83. if (name === 'Infinity') {
  84. property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
  85. }
  86. const problem = {
  87. node,
  88. messageId: PROPERTY_ERROR_MESSAGE_ID,
  89. data: {
  90. identifier: name,
  91. property,
  92. },
  93. };
  94. if (property === 'NEGATIVE_INFINITY') {
  95. problem.node = parent;
  96. problem.data.identifier = '-Infinity';
  97. problem.fix = function * (fixer) {
  98. yield fixer.replaceText(parent, 'Number.NEGATIVE_INFINITY');
  99. yield * fixSpaceAroundKeyword(fixer, parent, sourceCode);
  100. };
  101. } else {
  102. problem.fix = fixer => replaceReferenceIdentifier(node, `Number.${property}`, fixer, sourceCode);
  103. }
  104. reported.add(node);
  105. return problem;
  106. },
  107. };
  108. };
  109. const schema = [
  110. {
  111. type: 'object',
  112. additionalProperties: false,
  113. properties: {
  114. checkInfinity: {
  115. type: 'boolean',
  116. default: true,
  117. },
  118. },
  119. },
  120. ];
  121. /** @type {import('eslint').Rule.RuleModule} */
  122. module.exports = {
  123. create,
  124. meta: {
  125. type: 'suggestion',
  126. docs: {
  127. description: 'Prefer `Number` static properties over global ones.',
  128. },
  129. fixable: 'code',
  130. hasSuggestions: true,
  131. schema,
  132. messages,
  133. },
  134. };