Commit ab853db2 authored by Tooru Fujisawa's avatar Tooru Fujisawa
Browse files

Bug 1773747 - Part 6: Add ESLint rule to check immediately-used lazy getter. r=Standard8

parent 55a0bf77
Loading
Loading
Loading
Loading
+20 −7
Original line number Diff line number Diff line
@@ -2,8 +2,8 @@ valid-lazy
==========

Ensures that definitions and uses of properties on the ``lazy`` object are valid.
This rule checks for using unknown properties, duplicated symbols and unused
symbols.
This rule checks for using unknown properties, duplicated symbols, unused
symbols, and also lazy getter used at top-level unconditionally.

Examples of incorrect code for this rule:
-----------------------------------------
@@ -11,8 +11,10 @@ Examples of incorrect code for this rule:
.. code-block:: js

    const lazy = {};
    if (x) {
      // Unknown lazy member property {{name}}
      lazy.bar.foo();
    }

.. code-block:: js

@@ -21,7 +23,9 @@ Examples of incorrect code for this rule:

    // Duplicate symbol foo being added to lazy.
    XPCOMUtils.defineLazyGetter(lazy, "foo", "foo1.jsm");
    if (x) {
      lazy.foo3.bar();
    }

.. code-block:: js

@@ -29,6 +33,13 @@ Examples of incorrect code for this rule:
    // Unused lazy property foo
    XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");

.. code-block:: js

    const lazy = {};
    XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
    // Used at top-level unconditionally.
    lazy.foo.bar();

Examples of correct code for this rule:
---------------------------------------

@@ -38,5 +49,7 @@ Examples of correct code for this rule:
    XPCOMUtils.defineLazyGetter(lazy, "foo1", () => {});
    XPCOMUtils.defineLazyModuleGetters(lazy, { foo2: "foo2.jsm" });

    if (x) {
      lazy.foo1.bar();
      lazy.foo2.bar();
    }
+55 −0
Original line number Diff line number Diff line
@@ -533,6 +533,61 @@ module.exports = {
    return true;
  },

  /**
   * Check whether the node is evaluated at top-level script unconditionally.
   *
   * @param {Array} ancestors
   *        The parents of the current node.
   *
   * @return {Boolean}
   *         True or false
   */
  getIsTopLevelAndUnconditionallyExecuted(ancestors) {
    for (let parent of ancestors) {
      switch (parent.type) {
        // Control flow
        case "IfStatement":
        case "SwitchStatement":
        case "TryStatement":
        case "WhileStatement":
        case "DoWhileStatement":
        case "ForStatement":
        case "ForInStatement":
        case "ForOfStatement":
          return false;

        // Function
        case "FunctionDeclaration":
        case "FunctionExpression":
        case "ArrowFunctionExpression":
        case "ClassBody":
          return false;

        // Branch
        case "LogicalExpression":
        case "ConditionalExpression":
        case "ChainExpression":
          return false;

        case "AssignmentExpression":
          switch (parent.operator) {
            // Branch
            case "||=":
            case "&&=":
            case "??=":
              return false;
          }
          break;

        // Implicit branch (default value)
        case "ObjectPattern":
        case "ArrayPattern":
          return false;
      }
    }
    return true;
  },

  /**
   * Check whether we might be in a test head file.
   *
+13 −0
Original line number Diff line number Diff line
@@ -59,6 +59,8 @@ module.exports = {
      incorrectType: "Unexpected literal for property name {{name}}",
      unknownProperty: "Unknown lazy member property {{name}}",
      unusedProperty: "Unused lazy property {{name}}",
      topLevelAndUnconditional:
        "Lazy property {{name}} is used at top-level unconditionally. It should be non-lazy.",
    },
    type: "problem",
  },
@@ -178,6 +180,17 @@ module.exports = {
        } else {
          property.used = true;
        }
        if (
          helpers.getIsTopLevelAndUnconditionallyExecuted(
            context.getAncestors()
          )
        ) {
          context.report({
            node,
            messageId: "topLevelAndUnconditional",
            data: { name },
          });
        }
      },

      "Program:exit": function() {
+64 −11
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@
var rule = require("../lib/rules/valid-lazy");
var RuleTester = require("eslint").RuleTester;

const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 13 } });

// ------------------------------------------------------------------------------
// Tests
@@ -27,30 +27,30 @@ ruleTester.run("valid-lazy", rule, {
    `
      const lazy = {};
      XPCOMUtils.defineLazyGetter(lazy, "foo", () => {});
      lazy.foo.bar();
      if (x) { lazy.foo.bar(); }
    `,
    `
      const lazy = {};
      XPCOMUtils.defineLazyModuleGetters(lazy, {
        foo: "foo.jsm",
      });
      lazy.foo.bar();
      if (x) { lazy.foo.bar(); }
    `,
    `
      const lazy = {};
      ChromeUtils.defineESModuleGetters(lazy, {
        foo: "foo.mjs",
      });
      lazy.foo.bar();
      if (x) { lazy.foo.bar(); }
    `,
    `
      const lazy = {};
      Integration.downloads.defineModuleGetter(lazy, "foo", "foo.jsm");
      lazy.foo.bar();
      if (x) { lazy.foo.bar(); }
    `,
    `
      const lazy = createLazyLoaders({ foo: () => {}});
      lazy.foo.bar();
      if (x) { lazy.foo.bar(); }
    `,
    `
      const lazy = {};
@@ -60,18 +60,57 @@ ruleTester.run("valid-lazy", rule, {
        "bar",
        true
      );
      if (x) {
        lazy.foo1.bar();
        lazy.foo2.bar();
      }
    `,
    // Test for top-level unconditional.
    `
      const lazy = {};
      XPCOMUtils.defineLazyGetter(lazy, "foo", () => {});
      if (x) { lazy.foo.bar(); }
      for (;;) { lazy.foo.bar(); }
      for (var x in y) { lazy.foo.bar(); }
      for (var x of y) { lazy.foo.bar(); }
      while (true) { lazy.foo.bar(); }
      do { lazy.foo.bar(); } while (true);
      switch (x) { case 1: lazy.foo.bar(); }
      try { lazy.foo.bar(); } catch (e) {}
      function f() { lazy.foo.bar(); }
      (function f() { lazy.foo.bar(); });
      () => { lazy.foo.bar(); };
      class C {
        constructor() { lazy.foo.bar(); }
        foo() { lazy.foo.bar(); }
        get x() { lazy.foo.bar(); }
        set x(v) { lazy.foo.bar(); }
        a = lazy.foo.bar();
        #b = lazy.foo.bar();
        static {
          lazy.foo.bar();
        }
      }
      a && lazy.foo.bar();
      a || lazy.foo.bar();
      a ?? lazy.foo.bar();
      a ? lazy.foo.bar() : b;
      a?.b[lazy.foo.bar()];
      a ||= lazy.foo.bar();
      a &&= lazy.foo.bar();
      a ??= lazy.foo.bar();
      var { x = lazy.foo.bar() } = {};
      var [ y = lazy.foo.bar() ] = [];
    `,
  ],
  invalid: [
    invalidCode("lazy.bar", "bar", "unknownProperty"),
    invalidCode("if (x) { lazy.bar; }", "bar", "unknownProperty"),
    invalidCode(
      `
        const lazy = {};
        XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
        XPCOMUtils.defineLazyGetter(lazy, "foo", "foo1.jsm");
        lazy.foo.bar();
        if (x) { lazy.foo.bar(); }
      `,
      "foo",
      "duplicateSymbol"
@@ -82,7 +121,7 @@ ruleTester.run("valid-lazy", rule, {
        XPCOMUtils.defineLazyModuleGetters(lazy, {
          "foo-bar": "foo.jsm",
        });
        lazy["foo-bar"].bar();
        if (x) { lazy["foo-bar"].bar(); }
      `,
      "foo-bar",
      "incorrectType"
@@ -94,5 +133,19 @@ ruleTester.run("valid-lazy", rule, {
      "foo",
      "unusedProperty"
    ),
    invalidCode(
      `const lazy = {};
      XPCOMUtils.defineLazyGetter(lazy, "foo1", () => {});
      lazy.foo1.bar();`,
      "foo1",
      "topLevelAndUnconditional"
    ),
    invalidCode(
      `const lazy = {};
      XPCOMUtils.defineLazyGetter(lazy, "foo1", () => {});
      { x = -f(1 + lazy.foo1.bar()); }`,
      "foo1",
      "topLevelAndUnconditional"
    ),
  ],
});