From d0c482627bc945c1c3a24fc6d5e398dc3a27b357 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Sat, 12 Dec 2020 20:34:16 +0800 Subject: [PATCH] Add `prefer-date-now` rule (#935) --- docs/rules/prefer-date-now.md | 37 +++ index.js | 1 + readme.md | 2 + rules/prefer-date-now.js | 114 ++++++++ test/prefer-date-now.js | 104 +++++++ test/snapshots/prefer-date-now.js.md | 360 +++++++++++++++++++++++++ test/snapshots/prefer-date-now.js.snap | Bin 0 -> 1241 bytes 7 files changed, 618 insertions(+) create mode 100644 docs/rules/prefer-date-now.md create mode 100644 rules/prefer-date-now.js create mode 100644 test/prefer-date-now.js create mode 100644 test/snapshots/prefer-date-now.js.md create mode 100644 test/snapshots/prefer-date-now.js.snap diff --git a/docs/rules/prefer-date-now.md b/docs/rules/prefer-date-now.md new file mode 100644 index 0000000000..7dc8de7548 --- /dev/null +++ b/docs/rules/prefer-date-now.md @@ -0,0 +1,37 @@ +# Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch + +[`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now) is shorter and nicer than [`new Date().getTime()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTime). + +This rule is fixable. + +## Fail + +```js +const foo = new Date().getTime(); +``` + +```js +const foo = new Date().valueOf(); +``` + +```js +const foo = +new Date; +``` + +```js +const foo = Number(new Date()); +``` + +```js +const foo = new Date() * 2; +``` + +## Pass + +```js +const foo = Date.now(); +``` + +```js +const foo = Date.now() * 2; +``` diff --git a/index.js b/index.js index 6b0f38d5ec..e81861e43d 100644 --- a/index.js +++ b/index.js @@ -54,6 +54,7 @@ module.exports = { 'unicorn/prefer-add-event-listener': 'error', 'unicorn/prefer-array-find': 'error', 'unicorn/prefer-dataset': 'error', + 'unicorn/prefer-date-now': 'error', 'unicorn/prefer-event-key': 'error', // TODO: Enable this by default when targeting Node.js 12. 'unicorn/prefer-flat-map': 'off', diff --git a/readme.md b/readme.md index ee4b1b5a29..8783afa6c0 100644 --- a/readme.md +++ b/readme.md @@ -69,6 +69,7 @@ Configure it in `package.json`. "unicorn/prefer-add-event-listener": "error", "unicorn/prefer-array-find": "error", "unicorn/prefer-dataset": "error", + "unicorn/prefer-date-now": "error", "unicorn/prefer-event-key": "error", "unicorn/prefer-flat-map": "error", "unicorn/prefer-includes": "error", @@ -135,6 +136,7 @@ Configure it in `package.json`. - [prefer-add-event-listener](docs/rules/prefer-add-event-listener.md) - Prefer `.addEventListener()` and `.removeEventListener()` over `on`-functions. *(partly fixable)* - [prefer-array-find](docs/rules/prefer-array-find.md) - Prefer `.find(…)` over the first element from `.filter(…)`. *(partly fixable)* - [prefer-dataset](docs/rules/prefer-dataset.md) - Prefer using `.dataset` on DOM elements over `.setAttribute(…)`. *(fixable)* +- [prefer-date-now](docs/rules/prefer-date-now.md) - Prefer `Date.now()` to get the number of milliseconds since the Unix Epoch. *(fixable)* - [prefer-event-key](docs/rules/prefer-event-key.md) - Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. *(partly fixable)* - [prefer-flat-map](docs/rules/prefer-flat-map.md) - Prefer `.flatMap(…)` over `.map(…).flat()`. *(fixable)* - [prefer-includes](docs/rules/prefer-includes.md) - Prefer `.includes()` over `.indexOf()` when checking for existence or non-existence. *(fixable)* diff --git a/rules/prefer-date-now.js b/rules/prefer-date-now.js new file mode 100644 index 0000000000..70bc55a9f7 --- /dev/null +++ b/rules/prefer-date-now.js @@ -0,0 +1,114 @@ +'use strict'; +const getDocumentationUrl = require('./utils/get-documentation-url'); +const methodSelector = require('./utils/method-selector'); + +const MESSAGE_ID_DEFAULT = 'prefer-date'; +const MESSAGE_ID_METHOD = 'prefer-date-now-over-methods'; +const MESSAGE_ID_NUMBER = 'prefer-date-now-over-number-data-object'; +const messages = { + [MESSAGE_ID_DEFAULT]: 'Prefer `Date.now()` over `new Date()`.', + [MESSAGE_ID_METHOD]: 'Prefer `Date.now()` over `Date#{{method}}()`.', + [MESSAGE_ID_NUMBER]: 'Prefer `Date.now()` over `Number(new Date())`.' +}; + +const createNewDateSelector = path => { + const prefix = path ? `${path}.` : ''; + return [ + `[${prefix}type="NewExpression"]`, + `[${prefix}callee.type="Identifier"]`, + `[${prefix}callee.name="Date"]`, + `[${prefix}arguments.length=0]` + ].join(''); +}; + +const operatorsSelector = (...operators) => `:matches(${ + operators.map(operator => `[operator="${operator}"]`).join(', ') +})`; +const newDateSelector = createNewDateSelector(); +const methodsSelector = [ + methodSelector({ + names: ['getTime', 'valueOf'], + length: 0 + }), + createNewDateSelector('callee.object') +].join(''); +const builtinObjectSelector = [ + 'CallExpression', + '[callee.type="Identifier"]', + ':matches([callee.name="Number"], [callee.name="BigInt"])', + '[arguments.length=1]', + createNewDateSelector('arguments.0') +].join(''); +// https://github.com/estree/estree/blob/master/es5.md#unaryoperator +const unaryExpressionsSelector = [ + 'UnaryExpression', + operatorsSelector('+', '-'), + createNewDateSelector('argument') +].join(''); +const assignmentExpressionSelector = [ + 'AssignmentExpression', + operatorsSelector('-=', '*=', '/=', '%=', '**='), + '>', + `${newDateSelector}.right` +].join(''); +const binaryExpressionSelector = [ + 'BinaryExpression', + operatorsSelector('-', '*', '/', '%', '**'), + // Both `left` and `right` properties + '>', + newDateSelector +].join(''); + +const create = context => { + const report = (node, problem) => context.report({ + node, + messageId: MESSAGE_ID_DEFAULT, + fix: fixer => fixer.replaceText(node, 'Date.now()'), + ...problem + }); + + return { + [methodsSelector](node) { + const method = node.callee.property; + report(node, { + node: method, + messageId: MESSAGE_ID_METHOD, + data: {method: method.name} + }); + }, + [builtinObjectSelector](node) { + const {name} = node.callee; + if (name === 'Number') { + report(node, { + messageId: MESSAGE_ID_NUMBER + }); + } else { + report(node.arguments[0]); + } + }, + [unaryExpressionsSelector](node) { + report(node.operator === '-' ? node.argument : node); + }, + [assignmentExpressionSelector](node) { + report(node); + }, + [binaryExpressionSelector](node) { + report(node); + } + }; +}; + +const schema = []; + +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + url: getDocumentationUrl(__filename) + }, + fixable: 'code', + schema, + messages + } +}; diff --git a/test/prefer-date-now.js b/test/prefer-date-now.js new file mode 100644 index 0000000000..72f6cac06c --- /dev/null +++ b/test/prefer-date-now.js @@ -0,0 +1,104 @@ +import {test} from './utils/test'; + +test({ + valid: [ + 'const ts = Date.now()', + + // Test `new Date()` + // Not `NewExpression` + '+Date()', + '+ Date', + // Not `Identifier` + '+ new window.Date()', + // Not `Date` + '+ new Moments()', + // More arguments + '+ new Date(0)', + '+ new Date(...[])', + + // Test `new Date().getTime()` and `new Date().valueOf()` + // Not `CallExpression` + 'new Date.getTime()', + // Not `MemberExpression` + 'valueOf()', + // `computed` + 'new Date()[getTime]()', + // Not `Identifier` + 'new Date()["valueOf"]()', + // Not listed names + 'new Date().notListed(0)', + // More arguments + 'new Date().getTime(0)', + 'new Date().valueOf(...[])', + + // Test `Number(new Date())` and `BigInt(new Date())` + // Not `CallExpression` + 'new Number(new Date())', + // Not `Identifier` + 'window.BigInt(new Date())', + // Not listed names + 'toNumber(new Date())', + // More/less arguments + 'BigInt()', + 'Number(new Date(), extraArgument)', + 'BigInt([...new Date()])', + + // Test `+ new Date()` / `- new Date()` + // Not `UnaryExpression` + 'throw new Date()', + // Not `+/-` + 'typeof new Date()', + + // Test `AssignmentExpression` + // Not `AssignmentExpression` + 'const foo = () => {return new Date()}', + // `operator` not listed + 'foo += new Date()', + + // Test `BinaryExpression` + // Not `BinaryExpression` + 'function * foo() {yield new Date()}', + // `operator` not listed + 'new Date() + new Date()', + + // We are not checking these cases + 'foo = new Date() | 0', + 'foo &= new Date()', + 'foo = new Date() >> 0' + ], + invalid: [] +}); + +test.visualize([ + // `Date` methods + 'const ts = new Date().getTime();', + 'const ts = (new Date).getTime();', + 'const ts = (new Date()).getTime();', + 'const ts = new Date().valueOf();', + 'const ts = (new Date).valueOf();', + 'const ts = (new Date()).valueOf();', + + // `Number()` and `BigInt()` + 'const ts = /* 1 */ Number(/* 2 */ new /* 3 */ Date( /* 4 */ ) /* 5 */) /* 6 */', + 'const tsBigInt = /* 1 */ BigInt(/* 2 */ new /* 3 */ Date( /* 4 */ ) /* 5 */) /* 6 */', + + // `UnaryExpression` + 'const ts = + /* 1 */ new Date;', + 'const ts = - /* 1 */ new Date();', + + // `BinaryExpression` + 'const ts = new Date() - 0', + 'const foo = bar - new Date', + 'const foo = new Date() * bar', + 'const ts = new Date() / 1', + 'const ts = new Date() % Infinity', + 'const ts = new Date() ** 1', + 'const zero = (new Date(/* 1 */) /* 2 */) /* 3 */ - /* 4 */new Date', + + // `AssignmentExpression` + 'foo -= new Date()', + 'foo *= new Date()', + 'foo /= new Date', + 'foo %= new Date()', + 'foo **= new Date()' +]); diff --git a/test/snapshots/prefer-date-now.js.md b/test/snapshots/prefer-date-now.js.md new file mode 100644 index 0000000000..389714531c --- /dev/null +++ b/test/snapshots/prefer-date-now.js.md @@ -0,0 +1,360 @@ +# Snapshot report for `test/prefer-date-now.js` + +The actual snapshot is saved in `prefer-date-now.js.snap`. + +Generated by [AVA](https://avajs.dev). + +## prefer-date-now - #1 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = new Date().getTime();␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = new Date().getTime();␊ + | ^^^^^^^ Prefer `Date.now()` over `Date#getTime()`.␊ + ` + +## prefer-date-now - #2 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = (new Date).getTime();␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = (new Date).getTime();␊ + | ^^^^^^^ Prefer `Date.now()` over `Date#getTime()`.␊ + ` + +## prefer-date-now - #3 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = (new Date()).getTime();␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = (new Date()).getTime();␊ + | ^^^^^^^ Prefer `Date.now()` over `Date#getTime()`.␊ + ` + +## prefer-date-now - #4 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = new Date().valueOf();␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = new Date().valueOf();␊ + | ^^^^^^^ Prefer `Date.now()` over `Date#valueOf()`.␊ + ` + +## prefer-date-now - #5 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = (new Date).valueOf();␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = (new Date).valueOf();␊ + | ^^^^^^^ Prefer `Date.now()` over `Date#valueOf()`.␊ + ` + +## prefer-date-now - #6 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = (new Date()).valueOf();␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = (new Date()).valueOf();␊ + | ^^^^^^^ Prefer `Date.now()` over `Date#valueOf()`.␊ + ` + +## prefer-date-now - #7 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = /* 1 */ Number(/* 2 */ new /* 3 */ Date( /* 4 */ ) /* 5 */) /* 6 */␊ + ␊ + Output:␊ + 1 | const ts = /* 1 */ Date.now() /* 6 */␊ + ␊ + Error 1/1:␊ + > 1 | const ts = /* 1 */ Number(/* 2 */ new /* 3 */ Date( /* 4 */ ) /* 5 */) /* 6 */␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Date.now()` over `Number(new Date())`.␊ + ` + +## prefer-date-now - #8 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const tsBigInt = /* 1 */ BigInt(/* 2 */ new /* 3 */ Date( /* 4 */ ) /* 5 */) /* 6 */␊ + ␊ + Output:␊ + 1 | const tsBigInt = /* 1 */ BigInt(/* 2 */ Date.now() /* 5 */) /* 6 */␊ + ␊ + Error 1/1:␊ + > 1 | const tsBigInt = /* 1 */ BigInt(/* 2 */ new /* 3 */ Date( /* 4 */ ) /* 5 */) /* 6 */␊ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #9 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = + /* 1 */ new Date;␊ + ␊ + Output:␊ + 1 | const ts = Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = + /* 1 */ new Date;␊ + | ^^^^^^^^^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #10 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = - /* 1 */ new Date();␊ + ␊ + Output:␊ + 1 | const ts = - /* 1 */ Date.now();␊ + ␊ + Error 1/1:␊ + > 1 | const ts = - /* 1 */ new Date();␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #11 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = new Date() - 0␊ + ␊ + Output:␊ + 1 | const ts = Date.now() - 0␊ + ␊ + Error 1/1:␊ + > 1 | const ts = new Date() - 0␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #12 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const foo = bar - new Date␊ + ␊ + Output:␊ + 1 | const foo = bar - Date.now()␊ + ␊ + Error 1/1:␊ + > 1 | const foo = bar - new Date␊ + | ^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #13 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const foo = new Date() * bar␊ + ␊ + Output:␊ + 1 | const foo = Date.now() * bar␊ + ␊ + Error 1/1:␊ + > 1 | const foo = new Date() * bar␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #14 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = new Date() / 1␊ + ␊ + Output:␊ + 1 | const ts = Date.now() / 1␊ + ␊ + Error 1/1:␊ + > 1 | const ts = new Date() / 1␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #15 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = new Date() % Infinity␊ + ␊ + Output:␊ + 1 | const ts = Date.now() % Infinity␊ + ␊ + Error 1/1:␊ + > 1 | const ts = new Date() % Infinity␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #16 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const ts = new Date() ** 1␊ + ␊ + Output:␊ + 1 | const ts = Date.now() ** 1␊ + ␊ + Error 1/1:␊ + > 1 | const ts = new Date() ** 1␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #17 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | const zero = (new Date(/* 1 */) /* 2 */) /* 3 */ - /* 4 */new Date␊ + ␊ + Output:␊ + 1 | const zero = (Date.now() /* 2 */) /* 3 */ - /* 4 */Date.now()␊ + ␊ + Error 1/2:␊ + > 1 | const zero = (new Date(/* 1 */) /* 2 */) /* 3 */ - /* 4 */new Date␊ + | ^^^^^^^^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + Error 2/2:␊ + > 1 | const zero = (new Date(/* 1 */) /* 2 */) /* 3 */ - /* 4 */new Date␊ + | ^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #18 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | foo -= new Date()␊ + ␊ + Output:␊ + 1 | foo -= Date.now()␊ + ␊ + Error 1/1:␊ + > 1 | foo -= new Date()␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #19 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | foo *= new Date()␊ + ␊ + Output:␊ + 1 | foo *= Date.now()␊ + ␊ + Error 1/1:␊ + > 1 | foo *= new Date()␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #20 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | foo /= new Date␊ + ␊ + Output:␊ + 1 | foo /= Date.now()␊ + ␊ + Error 1/1:␊ + > 1 | foo /= new Date␊ + | ^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #21 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | foo %= new Date()␊ + ␊ + Output:␊ + 1 | foo %= Date.now()␊ + ␊ + Error 1/1:␊ + > 1 | foo %= new Date()␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` + +## prefer-date-now - #22 + +> Snapshot 1 + + `␊ + Input:␊ + 1 | foo **= new Date()␊ + ␊ + Output:␊ + 1 | foo **= Date.now()␊ + ␊ + Error 1/1:␊ + > 1 | foo **= new Date()␊ + | ^^^^^^^^^^ Prefer `Date.now()` over `new Date()`.␊ + ` diff --git a/test/snapshots/prefer-date-now.js.snap b/test/snapshots/prefer-date-now.js.snap new file mode 100644 index 0000000000000000000000000000000000000000..071adfdaf162de6310b3bc7b95b981b8b567283e GIT binary patch literal 1241 zcmV;~1Sb1IRzV2W-8`2o_BNny0;E(x#uSU%p(rIBkl)t@d9=u&6jQ1B30vclUXH z+V5@VT6**ai^M5Lu;>RMw)fGO@J)L5onymlRzCJs@*9To-#PwQ^2=ig!S0)IOLpROtLj9}4nHU@^$wGV?umakRRuYT>ivVX=- zMzClsI|DcU`(_`m?{o}(K!J^I_K$&ApZZ?z}YwnIMt-ry)v5FBaD#OXZ zFhO}m*1zqm6&HMa?8&;JMuQP7s?W{9Fxl(nb+xj*%XedY-~OG~r^pBv73N`Jn8qQ0 zmV;gIM{VGZN#1;Ck1~Qqp91kZi+2y|SDB{HdKmL0XGhc?MzH8bUIvCE@wY637#D2m zn-RXOV8V3|MzClLA21v~{R-f^?ri^t!PAh(Mp2a!ESkm7z;NF^le7QI&*i$}wV{#5 zn<^N=qOt-E4DW^6|84%`?(oa&;_1AKHI9s6QD#8~hCde>U!9Ti^C*5g*RaaMu!#{Y z+9<@p@Hh6<^}KVbJZ$*G6 zGO&YF3nMFoAkzT`F3-Gz(h@5!1qDNe8inNiyy6mtl41p0g}l^q1((EvT4D}7IxaOh4io~4Qqxk46cP}|Cn)5X zfkY67BuLTs-(uZ4a zO;l7^OOQfh0iW5~LqC^#Uj8N$0Q&=MeWT&k{QeqJ>?ZWh8j|^m$ zc)fwgV2tQM^BP$myNf%eX&`iI>ZPZagkIYk>^VqWi_HR8l~9`Us;J%-D`o$H+-cfQZoXo7H?^ZVKk;6 zDNLj+OxQeDm0FaKQlvq1jwUDz8NnFFpu7amRVF|d{>lPoB2ob0HVRwuWP}<%6uA&v zN+G)$0CAuZ%^gSzpeRXPC91iss^FQImYJ7XQb}B^8CgA%jb|h+7y&|IvOi3eAGLr{ zMx5^;Dv9(LSTVJIL~NsnM)eJv(fAWErF9Or#zjvVX; z0gf2zwqLLtOofQS*?!4q#OX^|qu8%BHz~D916n(Q-3Y6oz;1>Ol^objQ(kHgE(I=s< z(WgOG(}yE5Ic27M=9M5t9z=kuNs5pisOb#NNSt9tRhQ$h`$$NO#6}JPz+kUTFBbp+ D<)lM4 literal 0 HcmV?d00001