Bug 1347709 - Allow modules.json to be loaded from a local version for out-of-tree uses of eslint-plugin-mozilla. r?Mossop draft
authorMark Banner <standard8@mozilla.com>
Thu, 13 Apr 2017 12:03:30 +0100
changeset 563208 e4685bc31ad1f7d2e1e26ee856b56ceabde9283e
parent 562393 e1452316c4c12ccd509d782cf8a1ab3670275fab
child 624412 60c29e67a600bc956993923f4dbcbad93050f25b
push id54230
push userbmo:standard8@mozilla.com
push dateSat, 15 Apr 2017 10:58:10 +0000
reviewersMossop
bugs1347709
milestone55.0a1
Bug 1347709 - Allow modules.json to be loaded from a local version for out-of-tree uses of eslint-plugin-mozilla. r?Mossop MozReview-Commit-ID: 7RzAUqNJQ15
tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
tools/lint/eslint/eslint-plugin-mozilla/lib/environments/places-overlay.js
tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js
tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js
tools/lint/eslint/eslint-plugin-mozilla/package.json
tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
@@ -14,17 +14,17 @@
 // -----------------------------------------------------------------------------
 
 var fs = require("fs");
 var path = require("path");
 var helpers = require("../helpers");
 var globals = require("../globals");
 var placesGlobals = require("./places-overlay").globals;
 
-const rootDir = helpers.getRootDir(module.filename);
+const rootDir = helpers.rootDir;
 
 // These are scripts not included in global-scripts.inc, but which are loaded
 // via overlays.
 const EXTRA_SCRIPTS = [
   "browser/base/content/nsContextMenu.js",
   "toolkit/content/contentAreaUtils.js",
   "browser/components/places/content/editBookmarkOverlay.js",
   "browser/components/downloads/content/downloads.js",
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/places-overlay.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/places-overlay.js
@@ -11,19 +11,17 @@
 "use strict";
 
 // -----------------------------------------------------------------------------
 // Rule Definition
 // -----------------------------------------------------------------------------
 var path = require("path");
 var helpers = require("../helpers");
 var globals = require("../globals");
-var root = helpers.getRootDir(module.filename);
-var modules = require(path.join(root,
-                                "tools", "lint", "eslint", "modules.json"));
+var modules = helpers.modulesGlobalData;
 
 const placesOverlayFiles = [
   "toolkit/content/globalOverlay.js",
   "browser/base/content/utilityOverlay.js",
   "browser/components/places/content/controller.js",
   "browser/components/places/content/treeView.js"
 ];
 
@@ -43,17 +41,17 @@ const extraPlacesDefinitions = [
 
 const placesOverlayModules = [
   "PlacesUtils.jsm"
 ];
 
 function getScriptGlobals() {
   let fileGlobals = [];
   for (let file of placesOverlayFiles) {
-    let fileName = path.join(root, file);
+    let fileName = path.join(helpers.rootDir, file);
     try {
       fileGlobals = fileGlobals.concat(globals.getGlobalsForFile(fileName));
     } catch (e) {
       // The file isn't present, this is probably not an m-c repo.
     }
   }
 
   for (let file of placesOverlayModules) {
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js
@@ -22,17 +22,17 @@ const simpleTestFiles = [
   "MockObjects.js",
   "SimpleTest.js",
   "WindowSnapshot.js"
 ];
 const simpleTestPath = "testing/mochitest/tests/SimpleTest";
 
 function getScriptGlobals() {
   let fileGlobals = [];
-  let root = helpers.getRootDir(module.filename);
+  let root = helpers.rootDir;
   for (let file of simpleTestFiles) {
     let fileName = path.join(root, simpleTestPath, file);
     try {
       fileGlobals = fileGlobals.concat(globals.getGlobalsForFile(fileName));
     } catch (e) {
       // The files may not be available in non-m-c repositories.
       return [];
     }
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
@@ -72,17 +72,16 @@ var globalDiscoveryInProgressForFiles = 
  * Each returns an array of globals found.
  *
  * @param  {String} filePath
  *         The absolute path of the file being parsed.
  */
 function GlobalsForNode(filePath) {
   this.path = filePath;
   this.dirname = path.dirname(this.path);
-  this.root = helpers.getRootDir(this.path);
 }
 
 GlobalsForNode.prototype = {
   BlockComment(node, parents) {
     let value = node.value.trim();
     let match = /^import-globals-from\s+(.+)$/.exec(value);
     if (!match) {
       return [];
@@ -94,28 +93,28 @@ GlobalsForNode.prototype = {
       filePath = path.resolve(this.dirname, filePath);
     }
 
     return module.exports.getGlobalsForFile(filePath);
   },
 
   ExpressionStatement(node, parents, globalScope) {
     let isGlobal = helpers.getIsGlobalScope(parents);
-    let globals = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
+    let globals = helpers.convertExpressionToGlobals(node, isGlobal);
     // Map these globals now, as getGlobalsForFile is pre-mapped.
     globals = globals.map(name => {
       return { name, writable: true };
     });
 
     // Here we assume that if importScripts is set in the global scope, then
     // this is a worker. It would be nice if eslint gave us a way of getting
     // the environment directly.
     if (globalScope && globalScope.set.get("importScripts")) {
       let workerDetails = helpers.convertWorkerExpressionToGlobals(node,
-        isGlobal, this.root, this.dirname);
+        isGlobal, this.dirname);
       globals = globals.concat(workerDetails);
     }
 
     return globals;
   }
 };
 
 module.exports = {
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
@@ -7,17 +7,18 @@
 "use strict";
 
 var espree = require("espree");
 var estraverse = require("estraverse");
 var path = require("path");
 var fs = require("fs");
 var ini = require("ini-parser");
 
-var modules = null;
+var gModules = null;
+var gRootDir = null;
 var directoryManifests = new Map();
 
 var definitions = [
   /^loader\.lazyGetter\(this, "(\w+)"/,
   /^loader\.lazyImporter\(this, "(\w+)"/,
   /^loader\.lazyServiceGetter\(this, "(\w+)"/,
   /^loader\.lazyRequireGetter\(this, "(\w+)"/,
   /^XPCOMUtils\.defineLazyGetter\(this, "(\w+)"/,
@@ -35,16 +36,28 @@ var definitions = [
 
 var imports = [
   /^(?:Cu|Components\.utils)\.import\(".*\/((.*?)\.jsm?)"(?:, this)?\)/
 ];
 
 var workerImportFilenameMatch = /(.*\/)*(.*?\.jsm?)/;
 
 module.exports = {
+  get modulesGlobalData() {
+    if (!gModules) {
+      if (this.isMozillaCentralBased()) {
+        gModules = require(path.join(this.rootDir, "tools", "lint", "eslint", "modules.json"));
+      } else {
+        gModules = require("./modules.json");
+      }
+    }
+
+    return gModules;
+  },
+
   /**
    * Gets the abstract syntax tree (AST) of the JavaScript source code contained
    * in sourceText.
    *
    * @param  {String} sourceText
    *         Text containing valid JavaScript.
    *
    * @return {Object}
@@ -163,34 +176,28 @@ 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.
-   * @param  {String} repository
-   *         The root of the repository.
    *
    * @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, repository,
-                                             dirname) {
+  convertWorkerExpressionToGlobals(node, isGlobal, dirname) {
     var getGlobalsForFile = require("./globals").getGlobalsForFile;
 
-    if (!modules) {
-      modules = require(path.join(repository,
-        "tools", "lint", "eslint", "modules.json"));
-    }
+    let globalModules = this.modulesGlobalData;
 
     let results = [];
     let expr = node.expression;
 
     if (node.expression.type === "CallExpression" &&
         expr.callee &&
         expr.callee.type === "Identifier" &&
         expr.callee.name === "importScripts") {
@@ -198,34 +205,29 @@ module.exports = {
         var match = 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);
             }
-          } else if (match[2] in modules) {
-            results = results.concat(modules[match[2]].map(name => {
+          } else if (match[2] in globalModules) {
+            results = results.concat(globalModules[match[2]].map(name => {
               return { name, writable: true };
             }));
           }
         }
       }
     }
 
     return results;
   },
 
-  convertExpressionToGlobals(node, isGlobal, repository) {
-    if (!modules) {
-      modules = require(path.join(repository,
-        "tools", "lint", "eslint", "modules.json"));
-    }
-
+  convertExpressionToGlobals(node, isGlobal) {
     try {
       var source = this.getASTSource(node);
     } catch (e) {
       return [];
     }
 
     for (var reg of definitions) {
       let match = source.match(reg);
@@ -234,26 +236,28 @@ module.exports = {
         if (!isGlobal) {
           return [];
         }
 
         return [match[1]];
       }
     }
 
+    let globalModules = this.modulesGlobalData;
+
     for (reg of imports) {
       let match = source.match(reg);
       if (match) {
         // The two argument form is only acceptable in the global scope
         if (node.expression.arguments.length > 1 && !isGlobal) {
           return [];
         }
 
-        if (match[1] in modules) {
-          return modules[match[1]];
+        if (match[1] in globalModules) {
+          return globalModules[match[1]];
         }
 
         return [match[2]];
       }
     }
 
     return [];
   },
@@ -508,35 +512,36 @@ module.exports = {
 
   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 until
-   * a .eslintignore file is found.
-   * @param {String} fileName
-   *        The absolute path of a file in the repository
-   *
+   * Gets the root directory of the repository by walking up directories from
+   * this file until a .eslintignore file is found.
    * @return {String} The absolute path of the repository directory
    */
-  getRootDir(fileName) {
-    var dirName = path.dirname(fileName);
+  get rootDir() {
+    if (!gRootDir) {
+      let dirName = path.dirname(module.filename);
 
-    while (dirName && !fs.existsSync(path.join(dirName, ".eslintignore"))) {
-      dirName = path.dirname(dirName);
+      while (dirName && !fs.existsSync(path.join(dirName, ".eslintignore"))) {
+        dirName = path.dirname(dirName);
+      }
+
+      if (!dirName) {
+        throw new Error("Unable to find root of repository");
+      }
+
+      gRootDir = dirName;
     }
 
-    if (!dirName) {
-      throw new Error("Unable to find root of repository");
-    }
-
-    return dirName;
+    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
@@ -571,17 +576,17 @@ module.exports = {
    * context.getFileName contain leading and trailing double-quote characters.
    * These characters need to be removed.
    */
   cleanUpPath(pathName) {
     return pathName.replace(/^"/, "").replace(/"$/, "");
   },
 
   get globalScriptsPath() {
-    return path.join(this.getRootDir(module.filename), "browser",
+    return path.join(this.rootDir, "browser",
                      "base", "content", "global-scripts.inc");
   },
 
   isMozillaCentralBased() {
     return fs.existsSync(this.globalScriptsPath);
   },
 
   getSavedEnvironmentItems(environment) {
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js
@@ -20,17 +20,17 @@ var browserWindowEnv = require("../envir
 module.exports = function(context) {
   // ---------------------------------------------------------------------------
   // Public
   // ---------------------------------------------------------------------------
 
   return {
     Program(node) {
       let filePath = helpers.getAbsoluteFilePath(context);
-      let relativePath = path.relative(helpers.getRootDir(filePath), filePath);
+      let relativePath = path.relative(helpers.rootDir, filePath);
 
       if (browserWindowEnv.browserjsScripts &&
           browserWindowEnv.browserjsScripts.includes(relativePath)) {
         for (let global in browserWindowEnv.globals) {
           helpers.addVarToScope(global, context.getScope(),
                                 browserWindowEnv.globals[global]);
         }
       }
--- a/tools/lint/eslint/eslint-plugin-mozilla/package.json
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -1,11 +1,11 @@
 {
   "name": "eslint-plugin-mozilla",
-  "version": "0.2.37",
+  "version": "0.2.38",
   "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
   "keywords": [
     "eslint",
     "eslintplugin",
     "eslint-plugin",
     "mozilla",
     "firefox"
   ],
@@ -27,12 +27,13 @@
     "ini-parser": "^0.0.2",
     "sax": "^1.2.2"
   },
   "engines": {
     "node": ">=6.9.1"
   },
   "scripts": {
     "prepublishOnly": "node scripts/createExports.js",
-    "test": "node tests/test-run-all.js"
+    "test": "node tests/test-run-all.js",
+    "postpublish": "rm -f lib/modules.json lib/environments/saved-globals.json"
   },
   "license": "MPL-2.0"
 }
--- a/tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js
@@ -5,20 +5,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 "use strict";
 
 var fs = require("fs");
 var path = require("path");
 var helpers = require("../lib/helpers");
 
-const globalsFile = path.join(helpers.getRootDir(module.filename),
-                              "tools", "lint", "eslint", "eslint-plugin-mozilla",
+const eslintDir = path.join(helpers.getRootDir(module.filename),
+                            "tools", "lint", "eslint");
+
+const globalsFile = path.join(eslintDir, "eslint-plugin-mozilla",
                               "lib", "environments", "saved-globals.json");
 
+console.log("Copying modules.json");
+
+const modulesFile = path.join(eslintDir, "modules.json");
+const shipModulesFile = path.join(eslintDir, "eslint-plugin-mozilla", "lib",
+                                  "modules.json");
+
+fs.writeFileSync(shipModulesFile, fs.readFileSync(modulesFile));
+
 console.log("Generating globals file");
 
 // We only export the configs section.
 let environmentGlobals = require("../lib/index.js").environments;
 
 return fs.writeFile(globalsFile, JSON.stringify({environments: environmentGlobals}), err => {
   if (err) {
     console.error(err);