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

Differential Revision: https://phabricator.services.mozilla.com/D149867
This commit is contained in:
Tooru Fujisawa 2022-06-24 11:03:53 +00:00
parent 55a0bf7709
commit ab853db23f
4 changed files with 152 additions and 18 deletions

View File

@ -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();
}

View File

@ -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.
*

View File

@ -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() {

View File

@ -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"
),
],
});