prefer-number-properties.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. 'use strict';
  2. const {GlobalReferenceTracker} = require('./utils/global-reference-tracker.js');
  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.type === 'UnaryExpression' && parent.operator === '-' && parent.argument === node;
  24. };
  25. function checkProperty({node, path: [name]}, sourceCode) {
  26. const {parent} = node;
  27. let property = name;
  28. if (name === 'Infinity') {
  29. property = isNegative(node) ? 'NEGATIVE_INFINITY' : 'POSITIVE_INFINITY';
  30. }
  31. const problem = {
  32. node,
  33. messageId: MESSAGE_ID_ERROR,
  34. data: {
  35. description: name,
  36. property,
  37. },
  38. };
  39. if (property === 'NEGATIVE_INFINITY') {
  40. problem.node = parent;
  41. problem.data.description = '-Infinity';
  42. problem.fix = function * (fixer) {
  43. yield fixer.replaceText(parent, 'Number.NEGATIVE_INFINITY');
  44. yield * fixSpaceAroundKeyword(fixer, parent, sourceCode);
  45. };
  46. return problem;
  47. }
  48. const fix = fixer => replaceReferenceIdentifier(node, `Number.${property}`, fixer, sourceCode);
  49. const isSafeToFix = globalObjects[name];
  50. if (isSafeToFix) {
  51. problem.fix = fix;
  52. } else {
  53. problem.suggest = [
  54. {
  55. messageId: MESSAGE_ID_SUGGESTION,
  56. fix,
  57. },
  58. ];
  59. }
  60. return problem;
  61. }
  62. /** @param {import('eslint').Rule.RuleContext} context */
  63. const create = context => {
  64. const {
  65. checkInfinity,
  66. } = {
  67. checkInfinity: true,
  68. ...context.options[0],
  69. };
  70. const sourceCode = context.getSourceCode();
  71. let objects = Object.keys(globalObjects);
  72. if (!checkInfinity) {
  73. objects = objects.filter(name => name !== 'Infinity');
  74. }
  75. const tracker = new GlobalReferenceTracker({
  76. objects,
  77. handle: reference => checkProperty(reference, sourceCode),
  78. });
  79. return tracker.createListeners(context);
  80. };
  81. const schema = [
  82. {
  83. type: 'object',
  84. additionalProperties: false,
  85. properties: {
  86. checkInfinity: {
  87. type: 'boolean',
  88. default: true,
  89. },
  90. },
  91. },
  92. ];
  93. /** @type {import('eslint').Rule.RuleModule} */
  94. module.exports = {
  95. create,
  96. meta: {
  97. type: 'suggestion',
  98. docs: {
  99. description: 'Prefer `Number` static properties over global ones.',
  100. },
  101. fixable: 'code',
  102. hasSuggestions: true,
  103. schema,
  104. messages,
  105. },
  106. };