import { isCallExpression, isStringLiteral, } from './ast/index.js'; import { needsSemicolon, isParenthesized, } from './utils/index.js'; const MESSAGE_ID_ERROR = 'prefer-bigint-literals/error'; const MESSAGE_ID_SUGGESTION = 'prefer-bigint-literals/suggestion'; const messages = { [MESSAGE_ID_ERROR]: 'Prefer using bigint literal over `BigInt(…)`.', [MESSAGE_ID_SUGGESTION]: 'Replace with {{replacement}}.', }; const canUseNumericLiteralRaw = numericLiteral => { const raw = numericLiteral.raw.replaceAll('_', '').toLowerCase(); if (raw.includes('.')) { return false; } const {value} = numericLiteral; for (const {prefix, base} of [ {prefix: '0b', base: 2}, {prefix: '0o', base: 8}, {prefix: '0x', base: 16}, ]) { if (raw.startsWith(prefix)) { return raw.slice(2) === value.toString(base); } } if (raw.includes('e')) { return false; } return raw === String(value); }; function getReplacement(valueNode) { if (isStringLiteral(valueNode)) { const raw = valueNode.raw.slice(1, -1); let bigint; try { bigint = BigInt(raw); } catch { return; } let text = bigint === 0n ? '0' : raw.trim(); if (text.startsWith('+')) { text = text.slice(1).trim(); } return {shouldUseSuggestion: raw.includes('+'), text: `${text}n`, bigint}; } let shouldUseSuggestion = false; let isNegated = false; while (valueNode.type === 'UnaryExpression' && valueNode.prefix) { if (valueNode.operator === '+') { shouldUseSuggestion = true; valueNode = valueNode.argument; } else if (valueNode.operator === '-') { isNegated = !isNegated; valueNode = valueNode.argument; } else { return; } } const {value, raw} = valueNode; if (!Number.isInteger(value)) { return; } let bigint; try { bigint = BigInt(value); } catch { return; } let text; if (canUseNumericLiteralRaw(valueNode)) { text = `${raw}n`; } else { text = `${bigint}n`; shouldUseSuggestion = true; } if (isNegated && bigint !== 0n) { text = `-${text}`; bigint = -bigint; } return {shouldUseSuggestion, text, bigint}; } /** @param {import('eslint').Rule.RuleContext} context */ const create = context => { context.on('CallExpression', callExpression => { if (!isCallExpression(callExpression, { name: 'BigInt', argumentsLength: 1, optional: false, })) { return; } const [valueNode] = callExpression.arguments; const replacement = getReplacement(valueNode); if (!replacement) { return; } const problem = { node: callExpression, messageId: MESSAGE_ID_ERROR, }; const {shouldUseSuggestion, text, bigint} = replacement; /** @param {import('eslint').Rule.RuleFixer} fixer */ const fix = fixer => { let replacementText = text; if (!isParenthesized(callExpression, context) && bigint < 0n) { replacementText = `(${replacementText})`; const tokenBefore = context.sourceCode.getTokenBefore(callExpression); if (needsSemicolon(tokenBefore, context, replacementText)) { replacementText = `;${replacementText}`; } } return fixer.replaceText(callExpression, replacementText); }; if (shouldUseSuggestion || context.sourceCode.getCommentsInside(callExpression).length > 0) { problem.suggest = [ { messageId: MESSAGE_ID_SUGGESTION, data: { replacement: text.length < 20 ? `\`${text}\`` : 'a bigint literal', }, fix, }, ]; } else { problem.fix = fix; } return problem; }); }; /** @type {import('eslint').Rule.RuleModule} */ const config = { create, meta: { type: 'suggestion', docs: { description: 'Prefer `BigInt` literals over the constructor.', recommended: 'unopinionated', }, fixable: 'code', hasSuggestions: true, messages, }, }; export default config;