Bug 1330545 - [eslint] Handle importScripts in workers for determining global variables. r=mossop
authorMark Banner <standard8@mozilla.com>
Thu, 05 Jan 2017 07:31:13 +0000
changeset 331973 9ce2d81c539d989ec69239e506db6647a0b1f18e
parent 331972 977ade17796bdf7d80edc12b5738819f41383157
child 331974 5e23905ef624f1661588a4ddb370742ca8696352
push id31293
push userkwierso@gmail.com
push dateThu, 02 Feb 2017 00:07:12 +0000
treeherdermozilla-central@8196774c6b8a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmossop
bugs1330545
milestone54.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1330545 - [eslint] Handle importScripts in workers for determining global variables. r=mossop MozReview-Commit-ID: 6NkcAcaNjur
tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
tools/lint/eslint/eslint-plugin-mozilla/package.json
tools/lint/eslint/modules.json
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
@@ -60,57 +60,91 @@ function parseBooleanConfig(string, comm
 const globalCache = new Map();
 
 /**
  * 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
  * parents parameter which is a list of the parent nodes of the current node.
  * Each returns an array of globals found.
  *
- * @param  {String} path
+ * @param  {String} filePath
  *         The absolute path of the file being parsed.
  */
-function GlobalsForNode(path) {
-  this.path = path;
-  this.root = helpers.getRootDir(path);
+function GlobalsForNode(filePath) {
+  this.path = filePath;
+  this.dirname = path.dirname(this.path)
+  this.root = helpers.getRootDir(this.path);
+  this.isWorker = helpers.getIsWorker(this.path);
 }
 
 GlobalsForNode.prototype = {
+  Program(node) {
+    if (!this.isWorker) {
+      return [];
+    }
+
+    return [
+      {name: "importScripts", writable: false},
+      // Only available to workers.
+      {name: "FileReaderSync", writable: false},
+      {name: "onmessage", writable: true},
+      // Only available to chrome workers, but since we don't know which is which,
+      // we make it available anyway.
+      {name: "ctypes", writable: false}
+    ];
+  },
+
   BlockComment(node, parents) {
     let value = node.value.trim();
     let match = /^import-globals-from\s+(.+)$/.exec(value);
     if (!match) {
       return [];
     }
 
     let filePath = match[1].trim();
 
     if (!path.isAbsolute(filePath)) {
-      let dirName = path.dirname(this.path);
-      filePath = path.resolve(dirName, filePath);
+      filePath = path.resolve(this.dirname, filePath);
     }
 
     return module.exports.getGlobalsForFile(filePath);
   },
 
   ExpressionStatement(node, parents) {
     let isGlobal = helpers.getIsGlobalScope(parents);
-    let names = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
-    return names.map(name => { return { name, writable: true }});
+    let globals = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
+    // Map these globals now, as getGlobalsForFile is pre-mapped.
+    globals = globals.map(name => { return { name, writable: true }});
+
+    if (this.isWorker) {
+      let workerDetails = helpers.convertWorkerExpressionToGlobals(node,
+        isGlobal, this.root, this.dirname);
+      globals = globals.concat(workerDetails.map(name => {
+        return { name, writable: true };
+      }));
+    }
+
+    return globals;
   },
 };
 
 module.exports = {
   /**
    * Returns all globals for a given file. Recursively searches through
    * import-globals-from directives and also includes globals defined by
    * standard eslint directives.
    *
    * @param  {String} path
    *         The absolute path of the file to be parsed.
+   * @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.
    */
   getGlobalsForFile(path) {
     if (globalCache.has(path)) {
       return globalCache.get(path);
     }
 
     let content = fs.readFileSync(path, "utf8");
 
@@ -174,16 +208,19 @@ module.exports = {
       }
     };
 
     // Install thin wrappers around GlobalsForNode
     let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
 
     for (let type of Object.keys(GlobalsForNode.prototype)) {
       parser[type] = function(node) {
+        if (type === "Program") {
+          globalScope = context.getScope();
+        }
         let globals = handler[type](node, context.getAncestors());
         helpers.addGlobals(globals, globalScope);
       }
     }
 
     return parser;
   }
 };
--- a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
@@ -33,16 +33,18 @@ var definitions = [
   /^this\.__defineGetter__\("(\w+)"/,
   /^this\.(\w+) =/
 ];
 
 var imports = [
   /^(?:Cu|Components\.utils)\.import\(".*\/((.*?)\.jsm?)"(?:, this)?\)/,
 ];
 
+var workerImportFilenameMatch = /(.*\/)*(.*?\.jsm?)/;
+
 module.exports = {
   /**
    * Gets the abstract syntax tree (AST) of the JavaScript source code contained
    * in sourceText.
    *
    * @param  {String} sourceText
    *         Text containing valid JavaScript.
    *
@@ -163,18 +165,50 @@ module.exports = {
    * @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 variable names defined.
+   *         An array of global variable names defined.
    */
+  convertWorkerExpressionToGlobals: function(node, isGlobal, repository, dirname) {
+    var getGlobalsForFile = require("./globals").getGlobalsForFile;
+
+    if (!modules) {
+      modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json"));
+    }
+
+    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.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.push(modules[match[2]]);
+          }
+        }
+      }
+    }
+    return results;
+  },
+
   convertExpressionToGlobals: function(node, isGlobal, repository) {
     if (!modules) {
       modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json"));
     }
 
     try {
       var source = this.getASTSource(node);
     }
@@ -455,16 +489,22 @@ module.exports = {
 
     if (filename.startsWith("test_")) {
       return "xpcshell";
     }
 
     return null;
   },
 
+  getIsWorker: function(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
    *
    * @return {String} The absolute path of the repository directory
    */
--- 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.16",
+  "version": "0.2.17",
   "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
   "keywords": [
     "eslint",
     "eslintplugin",
     "eslint-plugin",
     "mozilla",
     "firefox"
   ],
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -65,16 +65,17 @@
   "engines.js": ["EngineManager", "Engine", "SyncEngine", "Tracker", "Store", "Changeset"],
   "enginesync.js": ["EngineSynchronizer"],
   "errors.js": ["BaseError", "ApplicationQuitError", "AssertionError", "TimeoutError"],
   "evaluate.js": ["evaluate", "sandbox", "Sandboxes"],
   "event-emitter.js": ["EventEmitter"],
   "EventUtils.js": ["disableNonTestMouseEvents", "sendMouseEvent", "sendChar", "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch", "synthesizeMouseAtPoint", "synthesizeTouchAtPoint", "synthesizeMouseAtCenter", "synthesizeTouchAtCenter", "synthesizeWheel", "synthesizeKey", "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent", "synthesizeText", "synthesizeComposition", "synthesizeQuerySelectedText"],
   "Extension.jsm": ["Extension", "ExtensionData"],
   "ExtensionAPI.jsm": ["ExtensionAPI", "ExtensionAPIs"],
+  "ExtensionsUI.jsm": ["ExtensionsUI"],
   "extension-storage.js": ["ExtensionStorageEngine", "EncryptionRemoteTransformer", "KeyRingEncryptionRemoteTransformer"],
   "ExtensionXPCShellUtils.jsm": ["ExtensionTestUtils"],
   "fakeservices.js": ["FakeCryptoService", "FakeFilesystemService", "FakeGUIDService", "fakeSHA256HMAC"],
   "file_expandosharing.jsm": ["checkFromJSM"],
   "file_stringencoding.jsm": ["checkFromJSM"],
   "file_url.jsm": ["checkFromJSM"],
   "file_worker_url.jsm": ["checkFromJSM"],
   "Finder.jsm": ["Finder", "GetClipboardSearchString"],
@@ -105,16 +106,17 @@
   "history.js": ["HistoryEngine", "HistoryRec"],
   "history.jsm": ["HistoryEntry", "DumpHistory"],
   "Http.jsm": ["httpRequest", "percentEncode"],
   "httpd.js": ["HTTP_400", "HTTP_401", "HTTP_402", "HTTP_403", "HTTP_404", "HTTP_405", "HTTP_406", "HTTP_407", "HTTP_408", "HTTP_409", "HTTP_410", "HTTP_411", "HTTP_412", "HTTP_413", "HTTP_414", "HTTP_415", "HTTP_417", "HTTP_500", "HTTP_501", "HTTP_502", "HTTP_503", "HTTP_504", "HTTP_505", "HttpError", "HttpServer"],
   "import_module.jsm": ["MODULE_IMPORTED", "MODULE_URI", "SUBMODULE_IMPORTED", "same_scope", "SUBMODULE_IMPORTED_TO_SCOPE"],
   "import_sub_module.jsm": ["SUBMODULE_IMPORTED", "test_obj"],
   "InlineSpellChecker.jsm": ["InlineSpellChecker", "SpellCheckHelper"],
   "JNI.jsm": ["JNI", "android_log"],
+  "JSDOMParser.js": ["JSDOMParser"],
   "Jsbeautify.jsm": ["jsBeautify"],
   "jsdebugger.jsm": ["addDebuggerToGlobal"],
   "json2.js": ["JSON"],
   "keys.js": ["BulkKeyBundle", "SyncKeyBundle"],
   "KeyValueParser.jsm": ["parseKeyValuePairsFromLines", "parseKeyValuePairs", "parseKeyValuePairsFromFile"],
   "kinto-http-client.js": ["KintoHttpClient"],
   "kinto-offline-client.js": ["Kinto"],
   "kinto-storage-adapter.js": ["FirefoxAdapter"],
@@ -144,17 +146,17 @@
   "NetworkPrioritizer.jsm": ["trackBrowserWindow"],
   "NotificationDB.jsm": [],
   "nsFormAutoCompleteResult.jsm": ["FormAutoCompleteResult"],
   "objects.js": ["getLength"],
   "observers.js": ["Observers"],
   "offlineAppCache.jsm": ["OfflineAppCacheHelper"],
   "OrientationChangeHandler.jsm": [],
   "os.js": ["listDirectory", "getFileForPath", "abspath", "getPlatform"],
-  "osfile.jsm": ["OS"],
+  "osfile.jsm": ["OS", "require"],
   "osfile_async_front.jsm": ["OS"],
   "osfile_native.jsm": ["read"],
   "osfile_shared_allthreads.jsm": ["LOG", "clone", "Config", "Constants", "Type", "HollowStructure", "OSError", "Library", "declareFFI", "declareLazy", "declareLazyFFI", "normalizeBufferArgs", "projectValue", "isArrayBuffer", "isTypedArray", "defineLazyGetter", "OS"],
   "osfile_unix_allthreads.jsm": ["declareFFI", "libc", "Error", "AbstractInfo", "AbstractEntry", "Type", "POS_START", "POS_CURRENT", "POS_END"],
   "osfile_win_allthreads.jsm": ["declareFFI", "libc", "Error", "AbstractInfo", "AbstractEntry", "Type", "POS_START", "POS_CURRENT", "POS_END"],
   "ospath_unix.jsm": ["basename", "dirname", "join", "normalize", "split", "toFileURI", "fromFileURI"],
   "ospath_win.jsm": ["basename", "dirname", "join", "normalize", "split", "winGetDrive", "winIsAbsolute", "toFileURI", "fromFileURI"],
   "OutputGenerator.jsm": ["UtteranceGenerator", "BrailleGenerator"],
@@ -171,27 +173,29 @@
   "PointerAdapter.jsm": ["PointerRelay", "PointerAdapter"],
   "policies.js": ["ErrorHandler", "SyncScheduler"],
   "prefs.js": ["PrefsEngine", "PrefRec"],
   "prefs.jsm": ["Preference"],
   "PresentationDeviceInfoManager.jsm": ["PresentationDeviceInfoService"],
   "PromiseWorker.jsm": ["BasePromiseWorker"],
   "PushCrypto.jsm": ["PushCrypto", "concatArray"],
   "quit.js": ["goQuitApplication"],
+  "Readability.js": ["Readability"],
   "record.js": ["WBORecord", "RecordManager", "CryptoWrapper", "CollectionKeyManager", "Collection"],
   "recursive_importA.jsm": ["foo", "bar"],
   "recursive_importB.jsm": ["baz", "qux"],
   "reflect.jsm": ["Reflect"],
   "RemoteFinder.jsm": ["RemoteFinder", "RemoteFinderListener"],
   "RemotePageManager.jsm": ["RemotePages", "RemotePageManager", "PageListener"],
   "RemoteWebProgress.jsm": ["RemoteWebProgressManager"],
   "resource.js": ["AsyncResource", "Resource"],
   "responsivedesign.jsm": ["ResponsiveUIManager"],
   "rest.js": ["RESTRequest", "RESTResponse", "TokenAuthenticatedRESTRequest", "SyncStorageRequest"],
   "rotaryengine.js": ["RotaryEngine", "RotaryRecord", "RotaryStore", "RotaryTracker"],
+  "require.js": ["require"],
   "RTCStatsReport.jsm": ["convertToRTCStatsReport"],
   "scratchpad-manager.jsm": ["ScratchpadManager"],
   "server.js": ["MarionetteServer"],
   "service.js": ["Service"],
   "SharedPromptUtils.jsm": ["PromptUtils", "EnableDelayHelper"],
   "ShutdownLeaksCollector.jsm": ["ContentCollector"],
   "SignInToWebsite.jsm": ["SignInToWebsiteController"],
   "Social.jsm": ["Social", "OpenGraphBuilder", "DynamicResizeWatcher", "sizeSocialPanelToContent"],