178 lines
3.7 KiB
JavaScript
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;
|