prefer-math-trunc.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. 'use strict';
  2. const {hasSideEffect} = require('@eslint-community/eslint-utils');
  3. const {fixSpaceAroundKeyword} = require('./fix/index.js');
  4. const ERROR_BITWISE = 'error-bitwise';
  5. const ERROR_BITWISE_NOT = 'error-bitwise-not';
  6. const SUGGESTION_BITWISE = 'suggestion-bitwise';
  7. const messages = {
  8. [ERROR_BITWISE]: 'Use `Math.trunc` instead of `{{operator}} {{value}}`.',
  9. [ERROR_BITWISE_NOT]: 'Use `Math.trunc` instead of `~~`.',
  10. [SUGGESTION_BITWISE]: 'Replace `{{operator}} {{value}}` with `Math.trunc`.',
  11. };
  12. const createBitwiseNotSelector = (level, isNegative) => {
  13. const prefix = 'argument.'.repeat(level);
  14. const selector = [
  15. `[${prefix}type="UnaryExpression"]`,
  16. `[${prefix}operator="~"]`,
  17. ].join('');
  18. return isNegative ? `:not(${selector})` : selector;
  19. };
  20. // Bitwise operators
  21. const bitwiseOperators = new Set(['|', '>>', '<<', '^']);
  22. // Unary Expression Selector: Inner-most 2 bitwise NOT
  23. const bitwiseNotUnaryExpressionSelector = [
  24. createBitwiseNotSelector(0),
  25. createBitwiseNotSelector(1),
  26. createBitwiseNotSelector(2, true),
  27. ].join('');
  28. /** @param {import('eslint').Rule.RuleContext} context */
  29. const create = context => {
  30. const sourceCode = context.getSourceCode();
  31. const mathTruncFunctionCall = node => {
  32. const text = sourceCode.getText(node);
  33. const parenthesized = node.type === 'SequenceExpression' ? `(${text})` : text;
  34. return `Math.trunc(${parenthesized})`;
  35. };
  36. return {
  37. ':matches(BinaryExpression, AssignmentExpression)[right.type="Literal"]'(node) {
  38. const {type, operator, right, left} = node;
  39. const isAssignment = type === 'AssignmentExpression';
  40. if (
  41. right.value !== 0
  42. || !bitwiseOperators.has(isAssignment ? operator.slice(0, -1) : operator)
  43. ) {
  44. return;
  45. }
  46. const problem = {
  47. node,
  48. messageId: ERROR_BITWISE,
  49. data: {
  50. operator,
  51. value: right.raw,
  52. },
  53. };
  54. if (!isAssignment || !hasSideEffect(left, sourceCode)) {
  55. const fix = function * (fixer) {
  56. const fixed = mathTruncFunctionCall(left);
  57. if (isAssignment) {
  58. const operatorToken = sourceCode.getTokenAfter(left, token => token.type === 'Punctuator' && token.value === operator);
  59. yield fixer.replaceText(operatorToken, '=');
  60. yield fixer.replaceText(right, fixed);
  61. } else {
  62. yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  63. yield fixer.replaceText(node, fixed);
  64. }
  65. };
  66. if (operator === '|') {
  67. problem.suggest = [
  68. {
  69. messageId: SUGGESTION_BITWISE,
  70. fix,
  71. },
  72. ];
  73. } else {
  74. problem.fix = fix;
  75. }
  76. }
  77. return problem;
  78. },
  79. [bitwiseNotUnaryExpressionSelector]: node => ({
  80. node,
  81. messageId: ERROR_BITWISE_NOT,
  82. * fix(fixer) {
  83. yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
  84. yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  85. },
  86. }),
  87. };
  88. };
  89. /** @type {import('eslint').Rule.RuleModule} */
  90. module.exports = {
  91. create,
  92. meta: {
  93. type: 'suggestion',
  94. docs: {
  95. description: 'Enforce the use of `Math.trunc` instead of bitwise operators.',
  96. },
  97. fixable: 'code',
  98. hasSuggestions: true,
  99. messages,
  100. },
  101. };