Files

178 lines
3.7 KiB
JavaScript

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;