crx2ff
Version:
Check the compatibility of a Chrome extension with the Firefox WebExtensions API.
99 lines (78 loc) • 2.56 kB
JavaScript
var fs = require('fs');
var esprima = require('esprima');
var estraverse = require('estraverse');
var propertyChain = [],
nodes = 0,
depth = 0,
foundChrome = false,
chromeDepth = 0,
chromeLocation = null,
propsSinceChrome = 0;
var apiExpressions;
// Build an AST of the provided file and look for chrome api expressions like
// - chrome.tabs.query(...)
// - foo(chrome.runtime.id)
// - if (chrome.runtime && chrome.runtime.lastError) { ... }
// - ...
//
// The result is an object whose keys represent unique API expressions found
// in the file, and key values containing the position of each given expression
// instance on the file.
function processFile (filepath) {
var fileContent = fs.readFileSync(filepath);
var tree = esprima.parse(fileContent, {
loc: true
});
apiExpressions = {};
estraverse.traverse(tree, {
enter: enter,
leave: leave
});
return apiExpressions;
}
function enter (node, parent) {
var parentIsMember,
parentIsCall;
++depth; ++nodes;
if (node.type !== "Identifier")
return;
parentIsMember = parent && parent.type === "MemberExpression";
parentIsCall = parent && parent.type === "CallExpression";
if (node.name === "chrome" && parentIsMember && parent.property !== node) {
foundChrome = true;
chromeLocation = node.loc;
chromeDepth = depth;
propsSinceChrome = nodes;
propertyChain.push(node.name);
} else if (node.name !== "chrome" && foundChrome && (!parentIsCall)
&& (depth < chromeDepth || nodes - propsSinceChrome === 1)) {
chromeDepth = depth;
propertyChain.push(node.name);
}
}
function leave (node, parent) {
var parentIsCall,
parentIsMember,
nodeIsCallMember;
--depth;
if (!foundChrome) {
return;
}
parentIsMember = parent && parent.type === "MemberExpression";
parentIsCall = parent && parent.type === "CallExpression";
nodeIsCallMember = parentIsCall && parent.callee === node;
if (parentIsCall && nodeIsCallMember || !parentIsMember) {
treatApiExpression(propertyChain, chromeLocation);
foundChrome = false;
propertyChain = [];
}
}
function treatApiExpression (propertyChain, location) {
var expr = propertyChain.join('.');
if (apiExpressions.hasOwnProperty(expr)) {
apiExpressions[expr].push(location.start);
} else {
apiExpressions[expr] = [location.start];
}
}
module.exports = processFile;