summaryrefslogtreecommitdiffstats
path: root/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js
diff options
context:
space:
mode:
Diffstat (limited to 'tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js')
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js221
1 files changed, 221 insertions, 0 deletions
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
new file mode 100644
index 0000000000..52ad4ce7d5
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js
@@ -0,0 +1,221 @@
+/**
+ * @fileoverview Ensures that definitions and uses of properties on the
+ * ``lazy`` object are valid.
+ *
+ * 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";
+const helpers = require("../helpers");
+
+const items = [
+ "loader",
+ "XPCOMUtils",
+ "Integration",
+ "ChromeUtils",
+ "DevToolsUtils",
+ "Object",
+ "Reflect",
+];
+
+const callExpressionDefinitions = [
+ /^loader\.lazyGetter\(lazy, "(\w+)"/,
+ /^loader\.lazyServiceGetter\(lazy, "(\w+)"/,
+ /^loader\.lazyRequireGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyGetter\(lazy, "(\w+)"/,
+ /^Integration\.downloads\.defineESModuleGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyModuleGetter\(lazy, "(\w+)"/,
+ /^ChromeUtils\.defineLazyGetter\(lazy, "(\w+)"/,
+ /^ChromeUtils\.defineModuleGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyPreferenceGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyProxy\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyScriptGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyServiceGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineConstant\(lazy, "(\w+)"/,
+ /^DevToolsUtils\.defineLazyModuleGetter\(lazy, "(\w+)"/,
+ /^DevToolsUtils\.defineLazyGetter\(lazy, "(\w+)"/,
+ /^Object\.defineProperty\(lazy, "(\w+)"/,
+ /^Reflect\.defineProperty\(lazy, "(\w+)"/,
+];
+
+const callExpressionMultiDefinitions = [
+ "ChromeUtils.defineESModuleGetters(lazy,",
+ "XPCOMUtils.defineLazyModuleGetters(lazy,",
+ "XPCOMUtils.defineLazyServiceGetters(lazy,",
+ "Object.defineProperties(lazy,",
+ "loader.lazyRequireGetter(lazy,",
+];
+
+module.exports = {
+ meta: {
+ docs: {
+ url: "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.html",
+ },
+ messages: {
+ duplicateSymbol: "Duplicate symbol {{name}} being added to lazy.",
+ 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.",
+ },
+ schema: [],
+ type: "problem",
+ },
+
+ create(context) {
+ let lazyProperties = new Map();
+ let unknownProperties = [];
+
+ function addProp(node, name) {
+ if (lazyProperties.has(name)) {
+ context.report({
+ node,
+ messageId: "duplicateSymbol",
+ data: { name },
+ });
+ return;
+ }
+ lazyProperties.set(name, { used: false, node });
+ }
+
+ function setPropertiesFromArgument(node, arg) {
+ if (arg.type === "ObjectExpression") {
+ for (let prop of arg.properties) {
+ if (prop.key.type == "Literal") {
+ context.report({
+ node,
+ messageId: "incorrectType",
+ data: { name: prop.key.value },
+ });
+ continue;
+ }
+ addProp(node, prop.key.name);
+ }
+ } else if (arg.type === "ArrayExpression") {
+ for (let prop of arg.elements) {
+ if (prop.type != "Literal") {
+ continue;
+ }
+ addProp(node, prop.value);
+ }
+ }
+ }
+
+ return {
+ VariableDeclarator(node) {
+ if (
+ node.id.type === "Identifier" &&
+ node.id.name == "lazy" &&
+ node.init.type == "CallExpression" &&
+ node.init.callee.name == "createLazyLoaders"
+ ) {
+ setPropertiesFromArgument(node, node.init.arguments[0]);
+ }
+ },
+
+ CallExpression(node) {
+ if (
+ node.callee.type != "MemberExpression" ||
+ (node.callee.object.type == "MemberExpression" &&
+ !items.includes(node.callee.object.object.name)) ||
+ (node.callee.object.type != "MemberExpression" &&
+ !items.includes(node.callee.object.name))
+ ) {
+ return;
+ }
+
+ let source;
+ try {
+ source = helpers.getASTSource(node);
+ } catch (e) {
+ return;
+ }
+
+ for (let reg of callExpressionDefinitions) {
+ let match = source.match(reg);
+ if (match) {
+ if (lazyProperties.has(match[1])) {
+ context.report({
+ node,
+ messageId: "duplicateSymbol",
+ data: { name: match[1] },
+ });
+ return;
+ }
+ lazyProperties.set(match[1], { used: false, node });
+ break;
+ }
+ }
+
+ if (
+ callExpressionMultiDefinitions.some(expr =>
+ source.startsWith(expr)
+ ) &&
+ node.arguments[1]
+ ) {
+ setPropertiesFromArgument(node, node.arguments[1]);
+ }
+ },
+
+ MemberExpression(node) {
+ if (node.computed || node.object.type !== "Identifier") {
+ return;
+ }
+
+ let name;
+ if (node.object.name == "lazy") {
+ name = node.property.name;
+ } else {
+ return;
+ }
+ let property = lazyProperties.get(name);
+ if (!property) {
+ // These will be reported on Program:exit - some definitions may
+ // be after first use, so we need to wait until we've processed
+ // the whole file before reporting.
+ unknownProperties.push({ name, node });
+ } else {
+ property.used = true;
+ }
+ if (
+ helpers.getIsTopLevelAndUnconditionallyExecuted(
+ context.getAncestors()
+ )
+ ) {
+ context.report({
+ node,
+ messageId: "topLevelAndUnconditional",
+ data: { name },
+ });
+ }
+ },
+
+ "Program:exit": function () {
+ for (let { name, node } of unknownProperties) {
+ let property = lazyProperties.get(name);
+ if (!property) {
+ context.report({
+ node,
+ messageId: "unknownProperty",
+ data: { name },
+ });
+ } else {
+ property.used = true;
+ }
+ }
+ for (let [name, property] of lazyProperties.entries()) {
+ if (!property.used) {
+ context.report({
+ node: property.node,
+ messageId: "unusedProperty",
+ data: { name },
+ });
+ }
+ }
+ },
+ };
+ },
+};