prefer-number-properties.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. 'use strict';
  2. const {ReferenceTracker} = require('eslint-utils');
  3. const {replaceReferenceIdentifier} = require('./fix/index.js');
  4. const {fixSpaceAroundKeyword} = require('./fix/index.js');
  5. const MESSAGE_ID_ERROR = 'error';
  6. const MESSAGE_ID_SUGGESTION = 'suggestion';
  7. const messages = {
  8. [MESSAGE_ID_ERROR]: 'Prefer `Number.{{property}}` over `{{description}}`.',
  9. [MESSAGE_ID_SUGGESTION]: 'Replace `{{description}}` with `Number.{{property}}`.',
  10. };
  11. const globalObjects = {
  12. // Safe to replace with `Number` properties
  13. parseInt: true,
  14. parseFloat: true,
  15. NaN: true,
  16. Infinity: true,
  17. // Unsafe to replace with `Number` properties
  18. isNaN: false,
  19. isFinite: false,
  20. };
  21. const isNegative = node => {
  22. const {parent} = node;
  23. return parent && parent.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
  24. };
  25. function * checkProperties({sourceCode, tracker, checkInfinity}) {
  26. let names = Object.keys(globalObjects);
  27. if (!checkInfinity) {
  28. names = names.filter(name => name !== 'Infinity');
  29. }
  30. const traceMap = Object.fromEntries(
  31. names.map(name => [name, {[ReferenceTracker.READ]: true}]),
  32. );
  33. for (const {node, path: [name]} of tracker.iterateGlobalReferences(traceMap)) {
  34. const {parent} = node;
  35. let property = name;
  36. if (name === 'Infinity') {
  37. property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
  38. }
  39. const problem = {
  40. node,
  41. messageId: MESSAGE_ID_ERROR,
  42. data: {
  43. description: name,
  44. property,
  45. },
  46. };
  47. if (property === 'NEGATIVE_INFINITY') {
  48. problem.node = parent;
  49. problem.data.description = '-Infinity';
  50. problem.fix = function * (fixer) {
  51. yield fixer.replaceText(parent, 'Number.NEGATIVE_INFINITY');
  52. yield * fixSpaceAroundKeyword(fixer, parent, sourceCode);
  53. };
  54. yield problem;
  55. continue;
  56. }
  57. const fix = fixer => replaceReferenceIdentifier(node, `Number.${property}`, fixer, sourceCode);
  58. const isSafeToFix = globalObjects[name];
  59. if (isSafeToFix) {
  60. problem.fix = fix;
  61. } else {
  62. problem.suggest = [
  63. {
  64. messageId: MESSAGE_ID_SUGGESTION,
  65. data: problem.data,
  66. fix,
  67. },
  68. ];
  69. }
  70. yield problem;
  71. }
  72. }
  73. /** @param {import('eslint').Rule.RuleContext} context */
  74. const create = context => {
  75. const {
  76. checkInfinity,
  77. } = {
  78. checkInfinity: true,
  79. ...context.options[0],
  80. };
  81. return {
  82. * 'Program:exit'() {
  83. const sourceCode = context.getSourceCode();
  84. const tracker = new ReferenceTracker(context.getScope());
  85. yield * checkProperties({sourceCode, tracker, checkInfinity});
  86. },
  87. };
  88. };
  89. const schema = [
  90. {
  91. type: 'object',
  92. additionalProperties: false,
  93. properties: {
  94. checkInfinity: {
  95. type: 'boolean',
  96. default: true,
  97. },
  98. },
  99. },
  100. ];
  101. /** @type {import('eslint').Rule.RuleModule} */
  102. module.exports = {
  103. create,
  104. meta: {
  105. type: 'suggestion',
  106. docs: {
  107. description: 'Prefer `Number` static properties over global ones.',
  108. },
  109. fixable: 'code',
  110. hasSuggestions: true,
  111. schema,
  112. messages,
  113. },
  114. };