prefer-numeric-literals.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. /**
  2. * @fileoverview Rule to disallow `parseInt()` in favor of binary, octal, and hexadecimal literals
  3. * @author Annie Zhang, Henry Zhu
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const radixMap = new Map([
  14. [2, { system: "binary", literalPrefix: "0b" }],
  15. [8, { system: "octal", literalPrefix: "0o" }],
  16. [16, { system: "hexadecimal", literalPrefix: "0x" }]
  17. ]);
  18. /**
  19. * Checks to see if a CallExpression's callee node is `parseInt` or
  20. * `Number.parseInt`.
  21. * @param {ASTNode} calleeNode The callee node to evaluate.
  22. * @returns {boolean} True if the callee is `parseInt` or `Number.parseInt`,
  23. * false otherwise.
  24. */
  25. function isParseInt(calleeNode) {
  26. switch (calleeNode.type) {
  27. case "Identifier":
  28. return calleeNode.name === "parseInt";
  29. case "MemberExpression":
  30. return calleeNode.object.type === "Identifier" &&
  31. calleeNode.object.name === "Number" &&
  32. calleeNode.property.type === "Identifier" &&
  33. calleeNode.property.name === "parseInt";
  34. // no default
  35. }
  36. return false;
  37. }
  38. //------------------------------------------------------------------------------
  39. // Rule Definition
  40. //------------------------------------------------------------------------------
  41. module.exports = {
  42. meta: {
  43. type: "suggestion",
  44. docs: {
  45. description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals",
  46. category: "ECMAScript 6",
  47. recommended: false,
  48. url: "https://eslint.org/docs/rules/prefer-numeric-literals"
  49. },
  50. schema: [],
  51. messages: {
  52. useLiteral: "Use {{system}} literals instead of {{functionName}}()."
  53. },
  54. fixable: "code"
  55. },
  56. create(context) {
  57. const sourceCode = context.getSourceCode();
  58. //----------------------------------------------------------------------
  59. // Public
  60. //----------------------------------------------------------------------
  61. return {
  62. "CallExpression[arguments.length=2]"(node) {
  63. const [strNode, radixNode] = node.arguments,
  64. str = strNode.value,
  65. radix = radixNode.value;
  66. if (
  67. strNode.type === "Literal" &&
  68. radixNode.type === "Literal" &&
  69. typeof str === "string" &&
  70. typeof radix === "number" &&
  71. radixMap.has(radix) &&
  72. isParseInt(node.callee)
  73. ) {
  74. const { system, literalPrefix } = radixMap.get(radix);
  75. context.report({
  76. node,
  77. messageId: "useLiteral",
  78. data: {
  79. system,
  80. functionName: sourceCode.getText(node.callee)
  81. },
  82. fix(fixer) {
  83. if (sourceCode.getCommentsInside(node).length) {
  84. return null;
  85. }
  86. const replacement = `${literalPrefix}${str}`;
  87. if (+replacement !== parseInt(str, radix)) {
  88. /*
  89. * If the newly-produced literal would be invalid, (e.g. 0b1234),
  90. * or it would yield an incorrect parseInt result for some other reason, don't make a fix.
  91. */
  92. return null;
  93. }
  94. const tokenBefore = sourceCode.getTokenBefore(node),
  95. tokenAfter = sourceCode.getTokenAfter(node);
  96. let prefix = "",
  97. suffix = "";
  98. if (
  99. tokenBefore &&
  100. tokenBefore.range[1] === node.range[0] &&
  101. !astUtils.canTokensBeAdjacent(tokenBefore, replacement)
  102. ) {
  103. prefix = " ";
  104. }
  105. if (
  106. tokenAfter &&
  107. node.range[1] === tokenAfter.range[0] &&
  108. !astUtils.canTokensBeAdjacent(replacement, tokenAfter)
  109. ) {
  110. suffix = " ";
  111. }
  112. return fixer.replaceText(node, `${prefix}${replacement}${suffix}`);
  113. }
  114. });
  115. }
  116. }
  117. };
  118. }
  119. };