| /** |
| * @fileoverview Rule to disallow `parseInt()` in favor of binary, octal, and hexadecimal literals |
| * @author Annie Zhang, Henry Zhu |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("./utils/ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| const radixMap = new Map([ |
| [2, { system: "binary", literalPrefix: "0b" }], |
| [8, { system: "octal", literalPrefix: "0o" }], |
| [16, { system: "hexadecimal", literalPrefix: "0x" }] |
| ]); |
| |
| /** |
| * Checks to see if a CallExpression's callee node is `parseInt` or |
| * `Number.parseInt`. |
| * @param {ASTNode} calleeNode The callee node to evaluate. |
| * @returns {boolean} True if the callee is `parseInt` or `Number.parseInt`, |
| * false otherwise. |
| */ |
| function isParseInt(calleeNode) { |
| return ( |
| astUtils.isSpecificId(calleeNode, "parseInt") || |
| astUtils.isSpecificMemberAccess(calleeNode, "Number", "parseInt") |
| ); |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| /** @type {import('../shared/types').Rule} */ |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "disallow `parseInt()` and `Number.parseInt()` in favor of binary, octal, and hexadecimal literals", |
| recommended: false, |
| url: "https://eslint.org/docs/rules/prefer-numeric-literals" |
| }, |
| |
| schema: [], |
| |
| messages: { |
| useLiteral: "Use {{system}} literals instead of {{functionName}}()." |
| }, |
| |
| fixable: "code" |
| }, |
| |
| create(context) { |
| const sourceCode = context.getSourceCode(); |
| |
| //---------------------------------------------------------------------- |
| // Public |
| //---------------------------------------------------------------------- |
| |
| return { |
| |
| "CallExpression[arguments.length=2]"(node) { |
| const [strNode, radixNode] = node.arguments, |
| str = astUtils.getStaticStringValue(strNode), |
| radix = radixNode.value; |
| |
| if ( |
| str !== null && |
| astUtils.isStringLiteral(strNode) && |
| radixNode.type === "Literal" && |
| typeof radix === "number" && |
| radixMap.has(radix) && |
| isParseInt(node.callee) |
| ) { |
| |
| const { system, literalPrefix } = radixMap.get(radix); |
| |
| context.report({ |
| node, |
| messageId: "useLiteral", |
| data: { |
| system, |
| functionName: sourceCode.getText(node.callee) |
| }, |
| fix(fixer) { |
| if (sourceCode.getCommentsInside(node).length) { |
| return null; |
| } |
| |
| const replacement = `${literalPrefix}${str}`; |
| |
| if (+replacement !== parseInt(str, radix)) { |
| |
| /* |
| * If the newly-produced literal would be invalid, (e.g. 0b1234), |
| * or it would yield an incorrect parseInt result for some other reason, don't make a fix. |
| * |
| * If `str` had numeric separators, `+replacement` will evaluate to `NaN` because unary `+` |
| * per the specification doesn't support numeric separators. Thus, the above condition will be `true` |
| * (`NaN !== anything` is always `true`) regardless of the `parseInt(str, radix)` value. |
| * Consequently, no autofixes will be made. This is correct behavior because `parseInt` also |
| * doesn't support numeric separators, but it does parse part of the string before the first `_`, |
| * so the autofix would be invalid: |
| * |
| * parseInt("1_1", 2) // === 1 |
| * 0b1_1 // === 3 |
| */ |
| return null; |
| } |
| |
| const tokenBefore = sourceCode.getTokenBefore(node), |
| tokenAfter = sourceCode.getTokenAfter(node); |
| let prefix = "", |
| suffix = ""; |
| |
| if ( |
| tokenBefore && |
| tokenBefore.range[1] === node.range[0] && |
| !astUtils.canTokensBeAdjacent(tokenBefore, replacement) |
| ) { |
| prefix = " "; |
| } |
| |
| if ( |
| tokenAfter && |
| node.range[1] === tokenAfter.range[0] && |
| !astUtils.canTokensBeAdjacent(replacement, tokenAfter) |
| ) { |
| suffix = " "; |
| } |
| |
| return fixer.replaceText(node, `${prefix}${replacement}${suffix}`); |
| } |
| }); |
| } |
| } |
| }; |
| } |
| }; |