Bug 1380003 - Create avoid-Date-timing eslint rule, r?standard8 draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Tue, 11 Jul 2017 12:34:20 -0400
changeset 608416 1d83c7cbac4d7fbcfe99ca560e7f7e1fd33180e5
parent 608415 d4e6f45db73333f693ac0cf53cab1f8ab4798373
child 608417 f85900852f3d285f4ba8d4add08f38b40ac85618
push id68257
push userahalberstadt@mozilla.com
push dateThu, 13 Jul 2017 15:10:41 +0000
reviewersstandard8
bugs1380003
milestone56.0a1
Bug 1380003 - Create avoid-Date-timing eslint rule, r?standard8 MozReview-Commit-ID: 6b6GtBUpZUZ
tools/lint/docs/linters/eslint-plugin-mozilla.rst
tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-Date-timing.js
tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-Date-timing.js
--- a/tools/lint/docs/linters/eslint-plugin-mozilla.rst
+++ b/tools/lint/docs/linters/eslint-plugin-mozilla.rst
@@ -34,16 +34,30 @@ the fact that `ctypes` can be accessed a
 frame-script
 ------------
 
 Defines the environment for frame scripts.
 
 Rules
 =====
 
+avoid-Date-timing
+-----------------
+
+Rejects grabbing the current time via Date.now() or new Date() for timing
+purposes when the less problematic performance.now() can be used instead.
+
+The performance.now() function returns milliseconds since page load. To
+convert that to milliseconds since the epoch, use:
+
+    performance.timing.navigationStart + performance.now()
+
+Often timing relative to the page load is adequate and that conversion may not
+be necessary.
+
 avoid-removeChild
 -----------------
 
 Rejects using element.parentNode.removeChild(element) when element.remove()
 can be used instead.
 
 avoid-nsISupportsString-preferences
 -----------------------------------
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
@@ -25,16 +25,17 @@ module.exports = {
     "frame-script": require("../lib/environments/frame-script.js"),
     "places-overlay": require("../lib/environments/places-overlay.js"),
     "simpletest": require("../lib/environments/simpletest.js")
   },
   processors: {
     ".xml": require("../lib/processors/xbl-bindings")
   },
   rules: {
+    "avoid-Date-timing": require("../lib/rules/avoid-Date-timing"),
     "avoid-removeChild": require("../lib/rules/avoid-removeChild"),
     "avoid-nsISupportsString-preferences":
       require("../lib/rules/avoid-nsISupportsString-preferences"),
     "balanced-listeners": require("../lib/rules/balanced-listeners"),
     "import-browser-window-globals":
       require("../lib/rules/import-browser-window-globals"),
     "import-content-task-globals":
       require("../lib/rules/import-content-task-globals"),
@@ -54,16 +55,17 @@ module.exports = {
       require("../lib/rules/reject-importGlobalProperties"),
     "reject-some-requires": require("../lib/rules/reject-some-requires"),
     "use-default-preference-values":
       require("../lib/rules/use-default-preference-values"),
     "use-ownerGlobal": require("../lib/rules/use-ownerGlobal"),
     "var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
   },
   rulesConfig: {
+    "avoid-Date-timing": "off",
     "avoid-removeChild": "off",
     "avoid-nsISupportsString-preferences": "off",
     "balanced-listeners": "off",
     "import-browser-window-globals": "off",
     "import-content-task-globals": "off",
     "import-globals": "off",
     "import-headjs-globals": "off",
     "mark-test-function-used": "off",
new file mode 100644
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-Date-timing.js
@@ -0,0 +1,58 @@
+/**
+ * @fileoverview Disallow using Date for timing in performance sensitive code
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = {
+  meta: {
+    docs: {
+      description: "disallow use of Date for timing measurements",
+      category: "Best Practices"
+    },
+    schema: []
+  },
+
+  // ---------------------------------------------------------------------------
+  // Public
+  //  --------------------------------------------------------------------------
+
+  create(context) {
+    return {
+      "CallExpression": function(node) {
+        let callee = node.callee;
+        if (callee.type !== "MemberExpression" ||
+            callee.object.type !== "Identifier" ||
+            callee.object.name !== "Date" ||
+            callee.property.type !== "Identifier" ||
+            callee.property.name !== "now") {
+          return;
+        }
+
+        context.report(node, "use performance.now() instead of Date.now() for timing " +
+                             "measurements");
+      },
+
+      "NewExpression": function(node) {
+        let callee = node.callee;
+        if (callee.type !== "Identifier" ||
+            callee.name !== "Date" ||
+            node.arguments.length > 0) {
+          return;
+        }
+
+        context.report(node, "use performance.now() instead of new Date() for timing " +
+                             "measurements");
+      }
+    };
+  }
+};
+
new file mode 100644
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-Date-timing.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/avoid-Date-timing");
+var RuleTester = require("eslint/lib/testers/rule-tester");
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, type, message) {
+  return {code, errors: [{message, type}]};
+}
+
+ruleTester.run("avoid-Date-timing", rule, {
+  valid: [
+    "new Date('2017-07-11');",
+    "new Date(1499790192440);",
+    "new Date(2017, 7, 11);",
+    "Date.UTC(2017, 7);"
+  ],
+  invalid: [
+    invalidCode("Date.now();", "CallExpression",
+                "use performance.now() instead of Date.now() " +
+                "for timing measurements"),
+    invalidCode("new Date();", "NewExpression",
+                "use performance.now() instead of new Date() " +
+                "for timing measurements")
+  ]
+});
+