123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- /**
- * @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}`);
- }
- });
- }
- }
- };
- }
- };
|