radix.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. /**
  2. * @fileoverview Rule to flag use of parseInt without a radix argument
  3. * @author James Allardice
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. const MODE_ALWAYS = "always",
  14. MODE_AS_NEEDED = "as-needed";
  15. /**
  16. * Checks whether a given variable is shadowed or not.
  17. * @param {eslint-scope.Variable} variable A variable to check.
  18. * @returns {boolean} `true` if the variable is shadowed.
  19. */
  20. function isShadowed(variable) {
  21. return variable.defs.length >= 1;
  22. }
  23. /**
  24. * Checks whether a given node is a MemberExpression of `parseInt` method or not.
  25. * @param {ASTNode} node A node to check.
  26. * @returns {boolean} `true` if the node is a MemberExpression of `parseInt`
  27. * method.
  28. */
  29. function isParseIntMethod(node) {
  30. return (
  31. node.type === "MemberExpression" &&
  32. !node.computed &&
  33. node.property.type === "Identifier" &&
  34. node.property.name === "parseInt"
  35. );
  36. }
  37. /**
  38. * Checks whether a given node is a valid value of radix or not.
  39. *
  40. * The following values are invalid.
  41. *
  42. * - A literal except numbers.
  43. * - undefined.
  44. * @param {ASTNode} radix A node of radix to check.
  45. * @returns {boolean} `true` if the node is valid.
  46. */
  47. function isValidRadix(radix) {
  48. return !(
  49. (radix.type === "Literal" && typeof radix.value !== "number") ||
  50. (radix.type === "Identifier" && radix.name === "undefined")
  51. );
  52. }
  53. /**
  54. * Checks whether a given node is a default value of radix or not.
  55. * @param {ASTNode} radix A node of radix to check.
  56. * @returns {boolean} `true` if the node is the literal node of `10`.
  57. */
  58. function isDefaultRadix(radix) {
  59. return radix.type === "Literal" && radix.value === 10;
  60. }
  61. //------------------------------------------------------------------------------
  62. // Rule Definition
  63. //------------------------------------------------------------------------------
  64. module.exports = {
  65. meta: {
  66. type: "suggestion",
  67. docs: {
  68. description: "enforce the consistent use of the radix argument when using `parseInt()`",
  69. category: "Best Practices",
  70. recommended: false,
  71. url: "https://eslint.org/docs/rules/radix"
  72. },
  73. schema: [
  74. {
  75. enum: ["always", "as-needed"]
  76. }
  77. ]
  78. },
  79. create(context) {
  80. const mode = context.options[0] || MODE_ALWAYS;
  81. /**
  82. * Checks the arguments of a given CallExpression node and reports it if it
  83. * offends this rule.
  84. * @param {ASTNode} node A CallExpression node to check.
  85. * @returns {void}
  86. */
  87. function checkArguments(node) {
  88. const args = node.arguments;
  89. switch (args.length) {
  90. case 0:
  91. context.report({
  92. node,
  93. message: "Missing parameters."
  94. });
  95. break;
  96. case 1:
  97. if (mode === MODE_ALWAYS) {
  98. context.report({
  99. node,
  100. message: "Missing radix parameter."
  101. });
  102. }
  103. break;
  104. default:
  105. if (mode === MODE_AS_NEEDED && isDefaultRadix(args[1])) {
  106. context.report({
  107. node,
  108. message: "Redundant radix parameter."
  109. });
  110. } else if (!isValidRadix(args[1])) {
  111. context.report({
  112. node,
  113. message: "Invalid radix parameter."
  114. });
  115. }
  116. break;
  117. }
  118. }
  119. return {
  120. "Program:exit"() {
  121. const scope = context.getScope();
  122. let variable;
  123. // Check `parseInt()`
  124. variable = astUtils.getVariableByName(scope, "parseInt");
  125. if (!isShadowed(variable)) {
  126. variable.references.forEach(reference => {
  127. const node = reference.identifier;
  128. if (astUtils.isCallee(node)) {
  129. checkArguments(node.parent);
  130. }
  131. });
  132. }
  133. // Check `Number.parseInt()`
  134. variable = astUtils.getVariableByName(scope, "Number");
  135. if (!isShadowed(variable)) {
  136. variable.references.forEach(reference => {
  137. const node = reference.identifier.parent;
  138. if (isParseIntMethod(node) && astUtils.isCallee(node)) {
  139. checkArguments(node.parent);
  140. }
  141. });
  142. }
  143. }
  144. };
  145. }
  146. };