1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015 |
- /**
- * @fileoverview A collection of helper functions.
- * 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 parser = require("@babel/eslint-parser");
- const { analyze } = require("eslint-scope");
- const { KEYS: defaultVisitorKeys } = require("eslint-visitor-keys");
- const estraverse = require("estraverse");
- const path = require("path");
- const fs = require("fs");
- const ini = require("multi-ini");
- 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 = {
- get iniParser() {
- if (!this._iniParser) {
- this._iniParser = new ini.Parser();
- }
- return this._iniParser;
- },
- get servicesData() {
- return require("./services.json");
- },
- /**
- * Obtains xpidl data from the object directory specified in the
- * environment.
- *
- * @returns {Map<string, object>}
- * A map of interface names to the interface details.
- */
- get xpidlData() {
- let xpidlDir;
- if (process.env.TASK_ID && !process.env.MOZ_XPT_ARTIFACTS_DIR) {
- throw new Error(
- "MOZ_XPT_ARTIFACTS_DIR must be set for this rule in automation"
- );
- }
- xpidlDir = process.env.MOZ_XPT_ARTIFACTS_DIR;
- if (!xpidlDir && process.env.MOZ_OBJDIR) {
- xpidlDir = `${process.env.MOZ_OBJDIR}/dist/xpt_artifacts/`;
- if (!fs.existsSync(xpidlDir)) {
- xpidlDir = `${process.env.MOZ_OBJDIR}/config/makefiles/xpidl/`;
- }
- }
- if (!xpidlDir) {
- throw new Error(
- "MOZ_OBJDIR must be defined in the environment for this rule, i.e. MOZ_OBJDIR=objdir-ff ./mach ..."
- );
- }
- if (xpidlData) {
- return xpidlData;
- }
- let files = fs.readdirSync(`${xpidlDir}`);
- // `Makefile` is an expected file in the directory.
- if (files.length <= 1) {
- throw new Error("Missing xpidl data files, maybe you need to build?");
- }
- xpidlData = new Map();
- for (let file of files) {
- if (!file.endsWith(".xpt")) {
- continue;
- }
- let data = JSON.parse(fs.readFileSync(path.join(`${xpidlDir}`, file)));
- for (let details of data) {
- xpidlData.set(details.name, details);
- }
- }
- return xpidlData;
- },
- /**
- * Gets the abstract syntax tree (AST) of the JavaScript source code contained
- * in sourceText. This matches the results for an eslint parser, see
- * https://eslint.org/docs/developer-guide/working-with-custom-parsers.
- *
- * @param {String} sourceText
- * Text containing valid JavaScript.
- * @param {Object} astOptions
- * Extra configuration to pass to the espree parser, these will override
- * the configuration from getPermissiveConfig().
- * @param {Object} configOptions
- * Extra options for getPermissiveConfig().
- *
- * @return {Object}
- * Returns an object containing `ast`, `scopeManager` and
- * `visitorKeys`
- */
- parseCode(sourceText, astOptions = {}, configOptions = {}) {
- // Use a permissive config file to allow parsing of anything that Espree
- // can parse.
- let config = { ...this.getPermissiveConfig(configOptions), ...astOptions };
- let parseResult =
- "parseForESLint" in parser
- ? parser.parseForESLint(sourceText, config)
- : { ast: parser.parse(sourceText, config) };
- let visitorKeys = parseResult.visitorKeys || defaultVisitorKeys;
- visitorKeys.ExperimentalRestProperty = visitorKeys.RestElement;
- visitorKeys.ExperimentalSpreadProperty = visitorKeys.SpreadElement;
- return {
- ast: parseResult.ast,
- scopeManager: parseResult.scopeManager || analyze(parseResult.ast),
- visitorKeys,
- };
- },
- /**
- * A simplistic conversion of some AST nodes to a standard string form.
- *
- * @param {Object} node
- * The AST node to convert.
- *
- * @return {String}
- * The JS source for the node.
- */
- getASTSource(node, context) {
- switch (node.type) {
- case "MemberExpression":
- if (node.computed) {
- let filename = context && context.getFilename();
- throw new Error(
- `getASTSource unsupported computed MemberExpression in ${filename}`
- );
- }
- return (
- this.getASTSource(node.object) +
- "." +
- this.getASTSource(node.property)
- );
- case "ThisExpression":
- return "this";
- case "Identifier":
- return node.name;
- case "Literal":
- return JSON.stringify(node.value);
- case "CallExpression":
- var args = node.arguments.map(a => this.getASTSource(a)).join(", ");
- return this.getASTSource(node.callee) + "(" + args + ")";
- case "ObjectExpression":
- return "{}";
- case "ExpressionStatement":
- return this.getASTSource(node.expression) + ";";
- case "FunctionExpression":
- return "function() {}";
- case "ArrayExpression":
- return "[" + node.elements.map(this.getASTSource, this).join(",") + "]";
- case "ArrowFunctionExpression":
- return "() => {}";
- case "AssignmentExpression":
- return (
- this.getASTSource(node.left) + " = " + this.getASTSource(node.right)
- );
- case "BinaryExpression":
- return (
- this.getASTSource(node.left) +
- " " +
- node.operator +
- " " +
- this.getASTSource(node.right)
- );
- case "UnaryExpression":
- return node.operator + " " + this.getASTSource(node.argument);
- default:
- throw new Error("getASTSource unsupported node type: " + node.type);
- }
- },
- /**
- * This walks an AST in a manner similar to ESLint passing node events to the
- * listener. The listener is expected to be a simple function
- * which accepts node type, node and parents arguments.
- *
- * @param {Object} ast
- * The AST to walk.
- * @param {Array} visitorKeys
- * The visitor keys to use for the AST.
- * @param {Function} listener
- * A callback function to call for the nodes. Passed three arguments,
- * event type, node and an array of parent nodes for the current node.
- */
- walkAST(ast, visitorKeys, listener) {
- let parents = [];
- estraverse.traverse(ast, {
- enter(node, parent) {
- listener(node.type, node, parents);
- parents.push(node);
- },
- leave(node, parent) {
- if (!parents.length) {
- throw new Error("Left more nodes than entered.");
- }
- parents.pop();
- },
- keys: visitorKeys,
- });
- if (parents.length) {
- throw new Error("Entered more nodes than left.");
- }
- },
- /**
- * 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.
- *
- * @param {String} name
- * The variable name to add to the scope.
- * @param {ASTScope} scope
- * The scope to add to.
- * @param {boolean} writable
- * Whether the global can be overwritten.
- * @param {Object} [node]
- * The AST node that defined the globals.
- */
- addVarToScope(name, scope, writable, node) {
- scope.__defineGeneric(name, scope.set, scope.variables, null, null);
- let variable = scope.set.get(name);
- variable.eslintExplicitGlobal = false;
- variable.writeable = writable;
- if (node) {
- variable.defs.push({
- type: "Variable",
- node,
- name: { name, parent: node.parent },
- });
- variable.identifiers.push(node);
- }
- // Walk to the global scope which holds all undeclared variables.
- while (scope.type != "global") {
- scope = scope.upper;
- }
- // "through" contains all references with no found definition.
- scope.through = scope.through.filter(function(reference) {
- if (reference.identifier.name != name) {
- return true;
- }
- // Links the variable and the reference.
- // And this reference is removed from `Scope#through`.
- reference.resolved = variable;
- variable.references.push(reference);
- return false;
- });
- },
- /**
- * Adds a set of globals to a scope.
- *
- * @param {Array} globalVars
- * An array of global variable names.
- * @param {ASTScope} scope
- * The scope.
- * @param {Object} [node]
- * The AST node that defined the globals.
- */
- addGlobals(globalVars, scope, node) {
- globalVars.forEach(v =>
- this.addVarToScope(v.name, scope, v.writable, v.explicit && node)
- );
- },
- /**
- * To allow espree to parse almost any JavaScript we need as many features as
- * possible turned on. This method returns that config.
- *
- * @param {Object} options
- * {
- * useBabel: {boolean} whether to set babelOptions.
- * }
- * @return {Object}
- * Espree compatible permissive config.
- */
- getPermissiveConfig({ useBabel = true } = {}) {
- const config = {
- range: true,
- requireConfigFile: false,
- babelOptions: {
- // configFile: path.join(gRootDir, ".babel-eslint.rc.js"),
- // parserOpts: {
- // plugins: [
- // "@babel/plugin-proposal-class-static-block",
- // "@babel/plugin-syntax-class-properties",
- // "@babel/plugin-syntax-jsx",
- // ],
- // },
- },
- loc: true,
- comment: true,
- attachComment: true,
- ecmaVersion: this.getECMAVersion(),
- sourceType: "script",
- };
- if (useBabel && this.isMozillaCentralBased()) {
- config.babelOptions.configFile = path.join(
- gRootDir,
- ".babel-eslint.rc.js"
- );
- }
- return config;
- },
- /**
- * Returns the ECMA version of the recommended config.
- *
- * @return {Number} The ECMA version of the recommended config.
- */
- getECMAVersion() {
- return recommendedConfig.parserOptions.ecmaVersion;
- },
- /**
- * Check whether it's inside top-level script.
- *
- * @param {Array} ancestors
- * The parents of the current node.
- *
- * @return {Boolean}
- * True or false
- */
- getIsTopLevelScript(ancestors) {
- for (let parent of ancestors) {
- switch (parent.type) {
- case "ArrowFunctionExpression":
- case "FunctionDeclaration":
- case "FunctionExpression":
- case "PropertyDefinition":
- case "StaticBlock":
- return false;
- }
- }
- return true;
- },
- isTopLevel(ancestors) {
- for (let parent of ancestors) {
- switch (parent.type) {
- case "ArrowFunctionExpression":
- case "FunctionDeclaration":
- case "FunctionExpression":
- case "PropertyDefinition":
- case "StaticBlock":
- case "BlockStatement":
- return false;
- }
- }
- return true;
- },
- /**
- * Check whether `this` expression points the global this.
- *
- * @param {Array} ancestors
- * The parents of the current node.
- *
- * @return {Boolean}
- * True or false
- */
- getIsGlobalThis(ancestors) {
- for (let parent of ancestors) {
- switch (parent.type) {
- case "FunctionDeclaration":
- case "FunctionExpression":
- case "PropertyDefinition":
- case "StaticBlock":
- return false;
- }
- }
- 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.
- *
- * @param {RuleContext} scope
- * You should pass this from within a rule
- * e.g. helpers.getIsHeadFile(context)
- *
- * @return {Boolean}
- * True or false
- */
- getIsHeadFile(scope) {
- var pathAndFilename = this.cleanUpPath(scope.getFilename());
- return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
- },
- /**
- * Gets the head files for a potential test file
- *
- * @param {RuleContext} scope
- * You should pass this from within a rule
- * e.g. helpers.getIsHeadFile(context)
- *
- * @return {String[]}
- * Paths to head files to load for the test
- */
- getTestHeadFiles(scope) {
- if (!this.getIsTest(scope)) {
- return [];
- }
- let filepath = this.cleanUpPath(scope.getFilename());
- let dir = path.dirname(filepath);
- let names = fs
- .readdirSync(dir)
- .filter(
- name =>
- (name.startsWith("head") || name.startsWith("xpcshell-head")) &&
- name.endsWith(".js")
- )
- .map(name => path.join(dir, name));
- return names;
- },
- /**
- * Gets all the test manifest data for a directory
- *
- * @param {String} dir
- * The directory
- *
- * @return {Array}
- * An array of objects with file and manifest properties
- */
- getManifestsForDirectory(dir) {
- if (directoryManifests.has(dir)) {
- return directoryManifests.get(dir);
- }
- let manifests = [];
- let names = [];
- try {
- names = fs.readdirSync(dir);
- } catch (err) {
- // Ignore directory not found, it might be faked by a test
- if (err.code !== "ENOENT") {
- throw err;
- }
- }
- for (let name of names) {
- if (!name.endsWith(".ini")) {
- continue;
- }
- try {
- let manifest = this.iniParser.parse(
- fs.readFileSync(path.join(dir, name), "utf8").split("\n")
- );
- manifests.push({
- file: path.join(dir, name),
- manifest,
- });
- } catch (e) {}
- }
- directoryManifests.set(dir, manifests);
- return manifests;
- },
- /**
- * Gets the manifest file a test is listed in
- *
- * @param {RuleContext} scope
- * You should pass this from within a rule
- * e.g. helpers.getIsHeadFile(context)
- *
- * @return {String}
- * The path to the test manifest file
- */
- getTestManifest(scope) {
- let filepath = this.cleanUpPath(scope.getFilename());
- let dir = path.dirname(filepath);
- let filename = path.basename(filepath);
- for (let manifest of this.getManifestsForDirectory(dir)) {
- if (filename in manifest.manifest) {
- return manifest.file;
- }
- }
- return null;
- },
- /**
- * Check whether we are in a test of some kind.
- *
- * @param {RuleContext} scope
- * You should pass this from within a rule
- * e.g. helpers.getIsTest(context)
- *
- * @return {Boolean}
- * True or false
- */
- getIsTest(scope) {
- // Regardless of the manifest name being in a manifest means we're a test.
- let manifest = this.getTestManifest(scope);
- if (manifest) {
- return true;
- }
- return !!this.getTestType(scope);
- },
- /*
- * Check if this is an .sjs file.
- */
- getIsSjs(scope) {
- let filepath = this.cleanUpPath(scope.getFilename());
- return path.extname(filepath) == ".sjs";
- },
- /**
- * Gets the type of test or null if this isn't a test.
- *
- * @param {RuleContext} scope
- * You should pass this from within a rule
- * e.g. helpers.getIsHeadFile(context)
- *
- * @return {String or null}
- * Test type: xpcshell, browser, chrome, mochitest
- */
- getTestType(scope) {
- let testTypes = ["browser", "xpcshell", "chrome", "mochitest", "a11y"];
- let manifest = this.getTestManifest(scope);
- if (manifest) {
- let name = path.basename(manifest);
- for (let testType of testTypes) {
- if (name.startsWith(testType)) {
- return testType;
- }
- }
- }
- let filepath = this.cleanUpPath(scope.getFilename());
- let filename = path.basename(filepath);
- if (filename.startsWith("browser_")) {
- return "browser";
- }
- if (filename.startsWith("test_")) {
- let parent = path.basename(path.dirname(filepath));
- for (let testType of testTypes) {
- if (parent.startsWith(testType)) {
- return testType;
- }
- }
- // It likely is a test, we're just not sure what kind.
- return "unknown";
- }
- // Likely not a test
- return null;
- },
- getIsWorker(filePath) {
- let filename = path.basename(this.cleanUpPath(filePath)).toLowerCase();
- return filename.includes("worker");
- },
- /**
- * Gets the root directory of the repository by walking up directories from
- * this file until a .eslintignore file is found. If this fails, the same
- * procedure will be attempted from the current working dir.
- * @return {String} The absolute path of the repository directory
- */
- get rootDir() {
- if (!gRootDir) {
- function searchUpForIgnore(dirName, filename) {
- let parsed = path.parse(dirName);
- while (parsed.root !== dirName) {
- if (fs.existsSync(path.join(dirName, filename))) {
- return dirName;
- }
- // Move up a level
- dirName = parsed.dir;
- parsed = path.parse(dirName);
- }
- return null;
- }
- let possibleRoot = searchUpForIgnore(
- path.dirname(module.filename),
- ".eslintignore"
- );
- if (!possibleRoot) {
- possibleRoot = searchUpForIgnore(path.resolve(), ".eslintignore");
- }
- if (!possibleRoot) {
- possibleRoot = searchUpForIgnore(path.resolve(), "package.json");
- }
- if (!possibleRoot) {
- // We've couldn't find a root from the module or CWD, so lets just go
- // for the CWD. We really don't want to throw if possible, as that
- // tends to give confusing results when used with ESLint.
- possibleRoot = process.cwd();
- }
- gRootDir = possibleRoot;
- }
- return gRootDir;
- },
- /**
- * ESLint may be executed from various places: from mach, at the root of the
- * repository, or from a directory in the repository when, for instance,
- * executed by a text editor's plugin.
- * The value returned by context.getFileName() varies because of this.
- * This helper function makes sure to return an absolute file path for the
- * current context, by looking at process.cwd().
- * @param {Context} context
- * @return {String} The absolute path
- */
- getAbsoluteFilePath(context) {
- var fileName = this.cleanUpPath(context.getFilename());
- var cwd = process.cwd();
- if (path.isAbsolute(fileName)) {
- // Case 2: executed from the repo's root with mach:
- // fileName: /path/to/mozilla/repo/a/b/c/d.js
- // cwd: /path/to/mozilla/repo
- return fileName;
- } else if (path.basename(fileName) == fileName) {
- // Case 1b: executed from a nested directory, fileName is the base name
- // without any path info (happens in Atom with linter-eslint)
- return path.join(cwd, fileName);
- }
- // Case 1: executed form in a nested directory, e.g. from a text editor:
- // fileName: a/b/c/d.js
- // cwd: /path/to/mozilla/repo/a/b/c
- var dirName = path.dirname(fileName);
- return cwd.slice(0, cwd.length - dirName.length) + fileName;
- },
- /**
- * When ESLint is run from SublimeText, paths retrieved from
- * context.getFileName contain leading and trailing double-quote characters.
- * These characters need to be removed.
- */
- cleanUpPath(pathName) {
- return pathName.replace(/^"/, "").replace(/"$/, "");
- },
- get globalScriptPaths() {
- return [
- path.join(this.rootDir, "browser", "base", "content", "browser.xhtml"),
- path.join(
- this.rootDir,
- "browser",
- "base",
- "content",
- "global-scripts.inc"
- ),
- ];
- },
- isMozillaCentralBased() {
- return fs.existsSync(this.globalScriptPaths[0]);
- },
- getSavedEnvironmentItems(environment) {
- return require("./environments/saved-globals.json").environments[
- environment
- ];
- },
- getSavedRuleData(rule) {
- return require("./rules/saved-rules-data.json").rulesData[rule];
- },
- getBuildEnvironment() {
- var { execFileSync } = require("child_process");
- var output = execFileSync(
- path.join(this.rootDir, "mach"),
- ["environment", "--format=json"],
- { silent: true }
- );
- return JSON.parse(output);
- },
- /**
- * Extract the path of require (and require-like) helpers used in DevTools.
- */
- getDevToolsRequirePath(node) {
- if (
- node.callee.type == "Identifier" &&
- node.callee.name == "require" &&
- node.arguments.length == 1 &&
- node.arguments[0].type == "Literal"
- ) {
- return node.arguments[0].value;
- } else if (
- node.callee.type == "MemberExpression" &&
- node.callee.property.type == "Identifier" &&
- node.callee.property.name == "lazyRequireGetter" &&
- node.arguments.length >= 3 &&
- node.arguments[2].type == "Literal"
- ) {
- return node.arguments[2].value;
- }
- return null;
- },
- /**
- * Returns property name from MemberExpression. Also accepts Identifier for consistency.
- * @param {import("estree").MemberExpression | import("estree").Identifier} node
- * @returns {string | null}
- *
- * @example `foo` gives "foo"
- * @example `foo.bar` gives "bar"
- * @example `foo.bar.baz` gives "baz"
- */
- maybeGetMemberPropertyName(node) {
- if (node.type === "MemberExpression") {
- return node.property.name;
- }
- if (node.type === "Identifier") {
- return node.name;
- }
- return null;
- },
- };
|