From cc23ce6654c0b372c536a7c5a1cd9245c9ada214 Mon Sep 17 00:00:00 2001 From: Mark Banner Date: Mon, 30 Jan 2023 19:33:36 +0000 Subject: [PATCH] Bug 1812977 - Move ESLint functions for globals handling from helpers.js to globals.js. r=Gijs This is a better location for these functions and helps avoid circular dependencies in the next patches. Differential Revision: https://phabricator.services.mozilla.com/D168067 --- .../eslint-plugin-mozilla/lib/globals.js | 217 +++++++++++++++++- .../eslint-plugin-mozilla/lib/helpers.js | 210 ----------------- 2 files changed, 211 insertions(+), 216 deletions(-) diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js index 2c43c187998b..a8b2f403e2ec 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js @@ -13,6 +13,40 @@ const fs = require("fs"); const helpers = require("./helpers"); const htmlparser = require("htmlparser2"); +const callExpressionDefinitions = [ + /^loader\.lazyGetter\((?:globalThis|this), "(\w+)"/, + /^loader\.lazyServiceGetter\((?:globalThis|this), "(\w+)"/, + /^loader\.lazyRequireGetter\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/, + /^ChromeUtils\.defineModuleGetter\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineLazyProxy\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|this), "(\w+)"/, + /^XPCOMUtils\.defineConstant\((?:globalThis|this), "(\w+)"/, + /^DevToolsUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/, + /^DevToolsUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/, + /^Object\.defineProperty\((?:globalThis|this), "(\w+)"/, + /^Reflect\.defineProperty\((?:globalThis|this), "(\w+)"/, + /^this\.__defineGetter__\("(\w+)"/, +]; + +const callExpressionMultiDefinitions = [ + "XPCOMUtils.defineLazyGlobalGetters(this,", + "XPCOMUtils.defineLazyGlobalGetters(globalThis,", + "XPCOMUtils.defineLazyModuleGetters(this,", + "XPCOMUtils.defineLazyModuleGetters(globalThis,", + "XPCOMUtils.defineLazyServiceGetters(this,", + "XPCOMUtils.defineLazyServiceGetters(globalThis,", + "ChromeUtils.defineESModuleGetters(this,", + "ChromeUtils.defineESModuleGetters(globalThis,", + "loader.lazyRequireGetter(this,", + "loader.lazyRequireGetter(globalThis,", +]; + +const workerImportFilenameMatch = /(.*\/)*((.*?)\.jsm?)/; + /** * Parses a list of "name:boolean_value" or/and "name" options divided by comma * or whitespace. @@ -72,6 +106,180 @@ var globalDiscoveryInProgressForFiles = new Set(); */ var lastHTMLGlobals = {}; +/** + * Attempts to convert an CallExpressions that look like module imports + * into global variable definitions. + * + * @param {Object} node + * The AST node to convert. + * @param {boolean} isGlobal + * True if the current node is in the global scope. + * + * @return {Array} + * An array of objects that contain details about the globals: + * - {String} name + * The name of the global. + * - {Boolean} writable + * If the global is writeable or not. + */ +function convertCallExpressionToGlobals(node, isGlobal) { + let express = node.expression; + if ( + express.type === "CallExpression" && + express.callee.type === "MemberExpression" && + express.callee.object && + express.callee.object.type === "Identifier" && + express.arguments.length === 1 && + express.arguments[0].type === "ArrayExpression" && + express.callee.property.type === "Identifier" && + express.callee.property.name === "importGlobalProperties" + ) { + return express.arguments[0].elements.map(literal => { + return { + explicit: true, + name: literal.value, + writable: false, + }; + }); + } + + let source; + try { + source = helpers.getASTSource(node); + } catch (e) { + return []; + } + + // The definition matches below must be in the global scope for us to define + // a global, so bail out early if we're not a global. + if (!isGlobal) { + return []; + } + + for (let reg of callExpressionDefinitions) { + let match = source.match(reg); + if (match) { + return [{ name: match[1], writable: true, explicit: true }]; + } + } + + if ( + callExpressionMultiDefinitions.some(expr => source.startsWith(expr)) && + node.expression.arguments[1] + ) { + let arg = node.expression.arguments[1]; + if (arg.type === "ObjectExpression") { + return arg.properties + .map(p => ({ + name: p.type === "Property" && p.key.name, + writable: true, + explicit: true, + })) + .filter(g => g.name); + } + if (arg.type === "ArrayExpression") { + return arg.elements + .map(p => ({ + name: p.type === "Literal" && p.value, + writable: true, + explicit: true, + })) + .filter(g => typeof g.name == "string"); + } + } + + if ( + node.expression.callee.type == "MemberExpression" && + node.expression.callee.property.type == "Identifier" && + node.expression.callee.property.name == "defineLazyScriptGetter" + ) { + // The case where we have a single symbol as a string has already been + // handled by the regexp, so we have an array of symbols here. + return node.expression.arguments[1].elements.map(n => ({ + name: n.value, + writable: true, + explicit: true, + })); + } + + return []; +} + +/** + * Attempts to convert an AssignmentExpression into a global variable + * definition if it applies to `this` in the global scope. + * + * @param {Object} node + * The AST node to convert. + * @param {boolean} isGlobal + * True if the current node is in the global scope. + * + * @return {Array} + * An array of objects that contain details about the globals: + * - {String} name + * The name of the global. + * - {Boolean} writable + * If the global is writeable or not. + */ +function convertThisAssignmentExpressionToGlobals(node, isGlobal) { + if ( + isGlobal && + node.expression.left && + node.expression.left.object && + node.expression.left.object.type === "ThisExpression" && + node.expression.left.property && + node.expression.left.property.type === "Identifier" + ) { + return [{ name: node.expression.left.property.name, writable: true }]; + } + return []; +} + +/** + * Attempts to convert an ExpressionStatement to likely global variable + * definitions. + * + * @param {Object} node + * The AST node to convert. + * @param {boolean} isGlobal + * True if the current node is in the global scope. + * + * @return {Array} + * An array of objects that contain details about the globals: + * - {String} name + * The name of the global. + * - {Boolean} writable + * If the global is writeable or not. + */ +function convertWorkerExpressionToGlobals(node, isGlobal, dirname) { + let results = []; + let expr = node.expression; + + if ( + node.expression.type === "CallExpression" && + expr.callee && + expr.callee.type === "Identifier" && + expr.callee.name === "importScripts" + ) { + for (var arg of expr.arguments) { + var match = arg.value && arg.value.match(workerImportFilenameMatch); + if (match) { + if (!match[1]) { + let filePath = path.resolve(dirname, match[2]); + if (fs.existsSync(filePath)) { + let additionalGlobals = module.exports.getGlobalsForFile(filePath); + results = results.concat(additionalGlobals); + } + } + // Import with relative/absolute path should explicitly use + // `import-globals-from` comment. + } + } + } + + return results; +} + /** * An object that returns found globals for given AST node types. Each prototype * property should be named for a node type and accepts a node parameter and a @@ -163,12 +371,9 @@ GlobalsForNode.prototype = { // Note: We check the expression types here and only call the necessary // functions to aid performance. if (node.expression.type === "AssignmentExpression") { - globals = helpers.convertThisAssignmentExpressionToGlobals( - node, - isGlobal - ); + globals = convertThisAssignmentExpressionToGlobals(node, isGlobal); } else if (node.expression.type === "CallExpression") { - globals = helpers.convertCallExpressionToGlobals(node, isGlobal); + globals = convertCallExpressionToGlobals(node, isGlobal); } // Here we assume that if importScripts is set in the global scope, then @@ -177,7 +382,7 @@ GlobalsForNode.prototype = { // // If this is testing context without path, ignore import. if (globalScope && globalScope.set.get("importScripts") && this.dirname) { - let workerDetails = helpers.convertWorkerExpressionToGlobals( + let workerDetails = convertWorkerExpressionToGlobals( node, isGlobal, this.dirname diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js index 823242cf92e2..31396bcef9cd 100644 --- a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js @@ -18,40 +18,6 @@ const recommendedConfig = require("./configs/recommended"); var gRootDir = null; var directoryManifests = new Map(); -const callExpressionDefinitions = [ - /^loader\.lazyGetter\((?:globalThis|this), "(\w+)"/, - /^loader\.lazyServiceGetter\((?:globalThis|this), "(\w+)"/, - /^loader\.lazyRequireGetter\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/, - /^ChromeUtils\.defineModuleGetter\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineLazyProxy\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|this), "(\w+)"/, - /^XPCOMUtils\.defineConstant\((?:globalThis|this), "(\w+)"/, - /^DevToolsUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/, - /^DevToolsUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/, - /^Object\.defineProperty\((?:globalThis|this), "(\w+)"/, - /^Reflect\.defineProperty\((?:globalThis|this), "(\w+)"/, - /^this\.__defineGetter__\("(\w+)"/, -]; - -const callExpressionMultiDefinitions = [ - "XPCOMUtils.defineLazyGlobalGetters(this,", - "XPCOMUtils.defineLazyGlobalGetters(globalThis,", - "XPCOMUtils.defineLazyModuleGetters(this,", - "XPCOMUtils.defineLazyModuleGetters(globalThis,", - "XPCOMUtils.defineLazyServiceGetters(this,", - "XPCOMUtils.defineLazyServiceGetters(globalThis,", - "ChromeUtils.defineESModuleGetters(this,", - "ChromeUtils.defineESModuleGetters(globalThis,", - "loader.lazyRequireGetter(this,", - "loader.lazyRequireGetter(globalThis,", -]; - -const workerImportFilenameMatch = /(.*\/)*((.*?)\.jsm?)/; - let xpidlData; module.exports = { @@ -251,182 +217,6 @@ module.exports = { } }, - /** - * Attempts to convert an ExpressionStatement to likely global variable - * definitions. - * - * @param {Object} node - * The AST node to convert. - * @param {boolean} isGlobal - * True if the current node is in the global scope. - * - * @return {Array} - * An array of objects that contain details about the globals: - * - {String} name - * The name of the global. - * - {Boolean} writable - * If the global is writeable or not. - */ - convertWorkerExpressionToGlobals(node, isGlobal, dirname) { - var getGlobalsForFile = require("./globals").getGlobalsForFile; - - let results = []; - let expr = node.expression; - - if ( - node.expression.type === "CallExpression" && - expr.callee && - expr.callee.type === "Identifier" && - expr.callee.name === "importScripts" - ) { - for (var arg of expr.arguments) { - var match = arg.value && arg.value.match(workerImportFilenameMatch); - if (match) { - if (!match[1]) { - let filePath = path.resolve(dirname, match[2]); - if (fs.existsSync(filePath)) { - let additionalGlobals = getGlobalsForFile(filePath); - results = results.concat(additionalGlobals); - } - } - // Import with relative/absolute path should explicitly use - // `import-globals-from` comment. - } - } - } - - return results; - }, - - /** - * Attempts to convert an AssignmentExpression into a global variable - * definition if it applies to `this` in the global scope. - * - * @param {Object} node - * The AST node to convert. - * @param {boolean} isGlobal - * True if the current node is in the global scope. - * - * @return {Array} - * An array of objects that contain details about the globals: - * - {String} name - * The name of the global. - * - {Boolean} writable - * If the global is writeable or not. - */ - convertThisAssignmentExpressionToGlobals(node, isGlobal) { - if ( - isGlobal && - node.expression.left && - node.expression.left.object && - node.expression.left.object.type === "ThisExpression" && - node.expression.left.property && - node.expression.left.property.type === "Identifier" - ) { - return [{ name: node.expression.left.property.name, writable: true }]; - } - return []; - }, - - /** - * Attempts to convert an CallExpressions that look like module imports - * into global variable definitions. - * - * @param {Object} node - * The AST node to convert. - * @param {boolean} isGlobal - * True if the current node is in the global scope. - * - * @return {Array} - * An array of objects that contain details about the globals: - * - {String} name - * The name of the global. - * - {Boolean} writable - * If the global is writeable or not. - */ - convertCallExpressionToGlobals(node, isGlobal) { - let express = node.expression; - if ( - express.type === "CallExpression" && - express.callee.type === "MemberExpression" && - express.callee.object && - express.callee.object.type === "Identifier" && - express.arguments.length === 1 && - express.arguments[0].type === "ArrayExpression" && - express.callee.property.type === "Identifier" && - express.callee.property.name === "importGlobalProperties" - ) { - return express.arguments[0].elements.map(literal => { - return { - explicit: true, - name: literal.value, - writable: false, - }; - }); - } - - let source; - try { - source = this.getASTSource(node); - } catch (e) { - return []; - } - - // The definition matches below must be in the global scope for us to define - // a global, so bail out early if we're not a global. - if (!isGlobal) { - return []; - } - - for (let reg of callExpressionDefinitions) { - let match = source.match(reg); - if (match) { - return [{ name: match[1], writable: true, explicit: true }]; - } - } - - if ( - callExpressionMultiDefinitions.some(expr => source.startsWith(expr)) && - node.expression.arguments[1] - ) { - let arg = node.expression.arguments[1]; - if (arg.type === "ObjectExpression") { - return arg.properties - .map(p => ({ - name: p.type === "Property" && p.key.name, - writable: true, - explicit: true, - })) - .filter(g => g.name); - } - if (arg.type === "ArrayExpression") { - return arg.elements - .map(p => ({ - name: p.type === "Literal" && p.value, - writable: true, - explicit: true, - })) - .filter(g => typeof g.name == "string"); - } - } - - if ( - node.expression.callee.type == "MemberExpression" && - node.expression.callee.property.type == "Identifier" && - node.expression.callee.property.name == "defineLazyScriptGetter" - ) { - // The case where we have a single symbol as a string has already been - // handled by the regexp, so we have an array of symbols here. - return node.expression.arguments[1].elements.map(n => ({ - name: n.value, - writable: true, - explicit: true, - })); - } - - return []; - }, - /** * Add a variable to the current scope. * HACK: This relies on eslint internals so it could break at any time.