diff --git a/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst index 3f6616369d13..fcbe5d064edd 100644 --- a/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst +++ b/docs/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.rst @@ -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 = {}; - // Unknown lazy member property {{name}} - lazy.bar.foo(); + 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"); - lazy.foo3.bar(); + 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" }); - lazy.foo1.bar(); - lazy.foo2.bar(); + if (x) { + lazy.foo1.bar(); + lazy.foo2.bar(); + } diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js index 390c4bf14bd8..e9576a242c9b 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js @@ -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. * diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js index ce34d9987da1..2579a45544b3 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js @@ -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() { diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js index 1b90d81c4f89..4796291dae9d 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js @@ -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 ); - lazy.foo1.bar(); - lazy.foo2.bar(); + 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" + ), ], });