prefer-date-now.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. 'use strict';
  2. const {
  3. matches,
  4. methodCallSelector,
  5. newExpressionSelector,
  6. callExpressionSelector,
  7. } = require('./selectors/index.js');
  8. const {fixSpaceAroundKeyword} = require('./fix/index.js');
  9. const MESSAGE_ID_DEFAULT = 'prefer-date';
  10. const MESSAGE_ID_METHOD = 'prefer-date-now-over-methods';
  11. const MESSAGE_ID_NUMBER = 'prefer-date-now-over-number-data-object';
  12. const messages = {
  13. [MESSAGE_ID_DEFAULT]: 'Prefer `Date.now()` over `new Date()`.',
  14. [MESSAGE_ID_METHOD]: 'Prefer `Date.now()` over `Date#{{method}}()`.',
  15. [MESSAGE_ID_NUMBER]: 'Prefer `Date.now()` over `Number(new Date())`.',
  16. };
  17. const createNewDateSelector = path => newExpressionSelector({path, name: 'Date', argumentsLength: 0});
  18. const operatorsSelector = (...operators) => matches(operators.map(operator => `[operator="${operator}"]`));
  19. // `new Date()`
  20. const newDateSelector = createNewDateSelector();
  21. // `new Date().{getTime,valueOf}()`
  22. const methodsSelector = [
  23. methodCallSelector({
  24. methods: ['getTime', 'valueOf'],
  25. argumentsLength: 0,
  26. }),
  27. createNewDateSelector('callee.object'),
  28. ].join('');
  29. // `{Number,BigInt}(new Date())`
  30. const builtinObjectSelector = [
  31. callExpressionSelector({names: ['Number', 'BigInt'], argumentsLength: 1}),
  32. createNewDateSelector('arguments.0'),
  33. ].join('');
  34. // https://github.com/estree/estree/blob/master/es5.md#unaryoperator
  35. const unaryExpressionsSelector = [
  36. 'UnaryExpression',
  37. operatorsSelector('+', '-'),
  38. createNewDateSelector('argument'),
  39. ].join('');
  40. const assignmentExpressionSelector = [
  41. 'AssignmentExpression',
  42. operatorsSelector('-=', '*=', '/=', '%=', '**='),
  43. '>',
  44. `${newDateSelector}.right`,
  45. ].join('');
  46. const binaryExpressionSelector = [
  47. 'BinaryExpression',
  48. operatorsSelector('-', '*', '/', '%', '**'),
  49. // Both `left` and `right` properties
  50. '>',
  51. newDateSelector,
  52. ].join('');
  53. const getProblem = (node, problem, sourceCode) => ({
  54. node,
  55. messageId: MESSAGE_ID_DEFAULT,
  56. * fix(fixer) {
  57. yield fixer.replaceText(node, 'Date.now()');
  58. if (node.type === 'UnaryExpression') {
  59. yield * fixSpaceAroundKeyword(fixer, node, sourceCode);
  60. }
  61. },
  62. ...problem,
  63. });
  64. /** @param {import('eslint').Rule.RuleContext} context */
  65. const create = context => ({
  66. [methodsSelector](node) {
  67. const method = node.callee.property;
  68. return getProblem(node, {
  69. node: method,
  70. messageId: MESSAGE_ID_METHOD,
  71. data: {method: method.name},
  72. });
  73. },
  74. [builtinObjectSelector](node) {
  75. const {name} = node.callee;
  76. if (name === 'Number') {
  77. return getProblem(node, {
  78. messageId: MESSAGE_ID_NUMBER,
  79. });
  80. }
  81. return getProblem(node.arguments[0]);
  82. },
  83. [unaryExpressionsSelector](node) {
  84. return getProblem(
  85. node.operator === '-' ? node.argument : node,
  86. {},
  87. context.getSourceCode(),
  88. );
  89. },
  90. [assignmentExpressionSelector](node) {
  91. return getProblem(node);
  92. },
  93. [binaryExpressionSelector](node) {
  94. return getProblem(node);
  95. },
  96. });
  97. /** @type {import('eslint').Rule.RuleModule} */
  98. module.exports = {
  99. create,
  100. meta: {
  101. type: 'suggestion',
  102. docs: {
  103. description: 'Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch.',
  104. },
  105. fixable: 'code',
  106. messages,
  107. },
  108. };