diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js | 276 |
1 files changed, 276 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..048ed17e3e --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js @@ -0,0 +1,276 @@ +/** + * @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+)"/, + /^ChromeUtils\.defineLazyGetter\(lazy, "(\w+)"/, + /^ChromeUtils\.defineModuleGetter\(lazy, "(\w+)"/, + /^XPCOMUtils\.defineLazyPreferenceGetter\(lazy, "(\w+)"/, + /^XPCOMUtils\.defineLazyScriptGetter\(lazy, "(\w+)"/, + /^XPCOMUtils\.defineLazyServiceGetter\(lazy, "(\w+)"/, + /^XPCOMUtils\.defineConstant\(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/rules/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 = []; + let isLazyExported = false; + + function getAncestorNodes(node) { + const ancestors = []; + node = node.parent; + while (node) { + ancestors.unshift(node); + node = node.parent; + } + return ancestors; + } + + // Returns true if lazy getter definitions in prevNode and currNode are + // duplicate. + // This returns false if prevNode and currNode have the same IfStatement as + // ancestor and they're in different branches. + function isDuplicate(prevNode, currNode) { + const prevAncestors = getAncestorNodes(prevNode); + const currAncestors = getAncestorNodes(currNode); + + for ( + let i = 0; + i < prevAncestors.length && i < currAncestors.length; + i++ + ) { + const prev = prevAncestors[i]; + const curr = currAncestors[i]; + if (prev === curr && prev.type === "IfStatement") { + if (prevAncestors[i + 1] !== currAncestors[i + 1]) { + return false; + } + } + } + + return true; + } + + function addProp(callNode, propNode, name) { + if ( + lazyProperties.has(name) && + isDuplicate(lazyProperties.get(name).callNode, callNode) + ) { + context.report({ + node: propNode, + messageId: "duplicateSymbol", + data: { name }, + }); + return; + } + lazyProperties.set(name, { used: false, callNode, propNode }); + } + + function setPropertiesFromArgument(callNode, arg) { + if (arg.type === "ObjectExpression") { + for (let propNode of arg.properties) { + if (propNode.key.type == "Literal") { + context.report({ + node: propNode, + messageId: "incorrectType", + data: { name: propNode.key.value }, + }); + continue; + } + addProp(callNode, propNode, propNode.key.name); + } + } else if (arg.type === "ArrayExpression") { + for (let propNode of arg.elements) { + if (propNode.type != "Literal") { + continue; + } + addProp(callNode, propNode, propNode.value); + } + } + } + + return { + VariableDeclarator(node) { + if ( + node.id.type === "Identifier" && + node.id.name == "lazy" && + node.init.type == "CallExpression" && + node.init.callee.name == "createLazyLoaders" + ) { + setPropertiesFromArgument(node.init, 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]) && + isDuplicate(lazyProperties.get(match[1]).callNode, node) + ) { + context.report({ + node, + messageId: "duplicateSymbol", + data: { name: match[1] }, + }); + return; + } + lazyProperties.set(match[1], { + used: false, + callNode: node, + propNode: 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 }, + }); + } + }, + + ExportNamedDeclaration(node) { + for (const spec of node.specifiers) { + if (spec.local.name === "lazy") { + // If the lazy object is exported, do not check unused property. + isLazyExported = true; + break; + } + } + }, + + "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; + } + } + if (!isLazyExported) { + for (let [name, property] of lazyProperties.entries()) { + if (!property.used) { + context.report({ + node: property.propNode, + messageId: "unusedProperty", + data: { name }, + }); + } + } + } + }, + }; + }, +}; |