Bug 1330545 - [eslint] Handle importScripts in workers for determining global variables. r=mossop

MozReview-Commit-ID: 6NkcAcaNjur

--HG--
extra : rebase_source : 2004e08b8e2e52b9ad329eb7604868ebe3bd60c6
This commit is contained in:
Mark Banner 2017-01-05 07:31:13 +00:00
parent 6a9f5ff102
commit 72976b4efa
4 changed files with 92 additions and 11 deletions

View File

@ -65,15 +65,33 @@ const globalCache = new Map();
* 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);
@ -84,8 +102,7 @@ GlobalsForNode.prototype = {
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);
@ -93,8 +110,19 @@ GlobalsForNode.prototype = {
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;
},
};
@ -106,6 +134,12 @@ module.exports = {
*
* @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)) {
@ -179,6 +213,9 @@ module.exports = {
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);
}

View File

@ -38,6 +38,8 @@ 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
@ -168,8 +170,40 @@ module.exports = {
* 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"));
@ -460,6 +494,12 @@ module.exports = {
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.

View File

@ -1,6 +1,6 @@
{
"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",

View File

@ -70,6 +70,7 @@
"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"],
@ -110,6 +111,7 @@
"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"],
@ -149,7 +151,7 @@
"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"],
@ -176,6 +178,7 @@
"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"],
@ -187,6 +190,7 @@
"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"],