111 lines
2.8 KiB
JavaScript
111 lines
2.8 KiB
JavaScript
import {hasSideEffect} from '@eslint-community/eslint-utils';
|
|
import {fixSpaceAroundKeyword} from './fix/index.js';
|
|
import {isLiteral} from './ast/index.js';
|
|
|
|
const ERROR_BITWISE = 'error-bitwise';
|
|
const ERROR_BITWISE_NOT = 'error-bitwise-not';
|
|
const SUGGESTION_BITWISE = 'suggestion-bitwise';
|
|
const messages = {
|
|
[ERROR_BITWISE]: 'Use `Math.trunc` instead of `{{operator}} {{value}}`.',
|
|
[ERROR_BITWISE_NOT]: 'Use `Math.trunc` instead of `~~`.',
|
|
[SUGGESTION_BITWISE]: 'Replace `{{operator}} {{value}}` with `Math.trunc`.',
|
|
};
|
|
|
|
// Bitwise operators
|
|
const bitwiseOperators = new Set(['|', '>>', '<<', '^']);
|
|
const isBitwiseNot = node =>
|
|
node.type === 'UnaryExpression'
|
|
&& node.operator === '~';
|
|
|
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
const create = context => {
|
|
const {sourceCode} = context;
|
|
|
|
const mathTruncFunctionCall = node => {
|
|
const text = sourceCode.getText(node);
|
|
const parenthesized = node.type === 'SequenceExpression' ? `(${text})` : text;
|
|
return `Math.trunc(${parenthesized})`;
|
|
};
|
|
|
|
context.on(['BinaryExpression', 'AssignmentExpression'], node => {
|
|
const {type, operator, right, left} = node;
|
|
const isAssignment = type === 'AssignmentExpression';
|
|
if (
|
|
!isLiteral(right, 0)
|
|
|| !bitwiseOperators.has(isAssignment ? operator.slice(0, -1) : operator)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const problem = {
|
|
node,
|
|
messageId: ERROR_BITWISE,
|
|
data: {
|
|
operator,
|
|
value: right.raw,
|
|
},
|
|
};
|
|
|
|
if (!isAssignment || !hasSideEffect(left, sourceCode)) {
|
|
const fix = function * (fixer) {
|
|
const fixed = mathTruncFunctionCall(left);
|
|
if (isAssignment) {
|
|
const operatorToken = sourceCode.getTokenAfter(left, token => token.type === 'Punctuator' && token.value === operator);
|
|
yield fixer.replaceText(operatorToken, '=');
|
|
yield fixer.replaceText(right, fixed);
|
|
} else {
|
|
yield fixSpaceAroundKeyword(fixer, node, context);
|
|
yield fixer.replaceText(node, fixed);
|
|
}
|
|
};
|
|
|
|
if (operator === '|') {
|
|
problem.suggest = [
|
|
{
|
|
messageId: SUGGESTION_BITWISE,
|
|
fix,
|
|
},
|
|
];
|
|
} else {
|
|
problem.fix = fix;
|
|
}
|
|
}
|
|
|
|
return problem;
|
|
});
|
|
|
|
// Unary Expression Selector: Inner-most 2 bitwise NOT
|
|
context.on('UnaryExpression', node => {
|
|
if (
|
|
isBitwiseNot(node)
|
|
&& isBitwiseNot(node.argument)
|
|
&& !isBitwiseNot(node.argument.argument)
|
|
) {
|
|
return {
|
|
node,
|
|
messageId: ERROR_BITWISE_NOT,
|
|
* fix(fixer) {
|
|
yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
|
|
yield fixSpaceAroundKeyword(fixer, node, context);
|
|
},
|
|
};
|
|
}
|
|
});
|
|
};
|
|
|
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
const config = {
|
|
create,
|
|
meta: {
|
|
type: 'suggestion',
|
|
docs: {
|
|
description: 'Enforce the use of `Math.trunc` instead of bitwise operators.',
|
|
recommended: 'unopinionated',
|
|
},
|
|
fixable: 'code',
|
|
hasSuggestions: true,
|
|
messages,
|
|
},
|
|
};
|
|
|
|
export default config;
|