prefer-math-trunc.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. 'use strict';
  2. const {hasSideEffect} = require('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. let fixed = mathTruncFunctionCall(left);
  57. if (isAssignment) {
  58. // TODO[@fisker]: Improve this fix, don't touch left
  59. fixed = `${sourceCode.getText(left)} = ${fixed}`;
  60. } else {
  61. yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  62. }
  63. yield fixer.replaceText(node, fixed);
  64. };
  65. if (operator === '|') {
  66. problem.suggest = [
  67. {
  68. messageId: SUGGESTION_BITWISE,
  69. data: {
  70. operator,
  71. value: right.raw,
  72. },
  73. fix,
  74. },
  75. ];
  76. } else {
  77. problem.fix = fix;
  78. }
  79. }
  80. return problem;
  81. },
  82. [bitwiseNotUnaryExpressionSelector]: node => ({
  83. node,
  84. messageId: ERROR_BITWISE_NOT,
  85. * fix(fixer) {
  86. yield fixer.replaceText(node, mathTruncFunctionCall(node.argument.argument));
  87. yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  88. },
  89. }),
  90. };
  91. };
  92. /** @type {import('eslint').Rule.RuleModule} */
  93. module.exports = {
  94. create,
  95. meta: {
  96. type: 'suggestion',
  97. docs: {
  98. description: 'Enforce the use of `Math.trunc` instead of bitwise operators.',
  99. },
  100. fixable: 'code',
  101. hasSuggestions: true,
  102. messages,
  103. },
  104. };