File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
var through = require('through2');
var Readable = require('readable-stream').Readable;
var concat = require('concat-stream');
var duplexer = require('duplexer2');
var acorn = require('acorn-node');
var walkAst = require('acorn-node/walk').full;
var scan = require('scope-analyzer');
var unparse = require('escodegen').generate;
var inspect = require('object-inspect');
var evaluate = require('static-eval');
var copy = require('shallow-copy');
var has = require('has');
var MagicString = require('magic-string');
var convertSourceMap = require('convert-source-map');
var mergeSourceMap = require('merge-source-map');
module.exports = function parse (modules, opts) {
if (!opts) opts = {};
var vars = opts.vars || {};
var varModules = opts.varModules || {};
var parserOpts = copy(opts.parserOpts || {});
var updates = [];
var moduleBindings = [];
var sourcemapper;
var inputMap;
var output = through();
var body, ast;
return duplexer(concat({ encoding: 'buffer' }, function (buf) {
try {
body = buf.toString('utf8').replace(/^#!/, '//#!');
var matches = false;
for (var key in modules) {
if (body.indexOf(key) !== -1) {
matches = true;
break;
}
}
if (!matches) {
// just pass it through
output.end(buf);
return;
}
if (opts.sourceMap) {
inputMap = convertSourceMap.fromSource(body);
if (inputMap) inputMap = inputMap.toObject();
body = convertSourceMap.removeComments(body);
sourcemapper = new MagicString(body);
}
ast = acorn.parse(body, parserOpts);
// scan.crawl does .parent tracking, so we can use acorn's builtin walker.
scan.crawl(ast);
walkAst(ast, walk);
}
catch (err) { return error(err) }
finish(body);
}), output);
function finish (src) {
var pos = 0;
src = String(src);
moduleBindings.forEach(function (binding) {
if (binding.isReferenced()) {
return;
}
var node = binding.initializer;
if (node.type === 'VariableDeclarator') {
var i = node.parent.declarations.indexOf(node);
if (node.parent.declarations.length === 1) {
// remove the entire declaration
updates.push({
start: node.parent.start,
offset: node.parent.end - node.parent.start,
stream: st()
});
} else if (i === node.parent.declarations.length - 1) {
updates.push({
// remove ", a = 1"
start: node.parent.declarations[i - 1].end,
offset: node.end - node.parent.declarations[i - 1].end,
stream: st()
});
} else {
updates.push({
// remove "a = 1, "
start: node.start,
offset: node.parent.declarations[i + 1].start - node.start,
stream: st()
});
}
} else if (node.parent.type === 'SequenceExpression' && node.parent.expressions.length > 1) {
var i = node.parent.expressions.indexOf(node);
if (i === node.parent.expressions.length - 1) {
updates.push({
// remove ", a = 1"
start: node.parent.expressions[i - 1].end,
offset: node.end - node.parent.expressions[i - 1].end,
stream: st()
});
} else {
updates.push({
// remove "a = 1, "
start: node.start,
offset: node.parent.expressions[i + 1].start - node.start,
stream: st()
});
}
} else {
if (node.parent.type === 'ExpressionStatement') node = node.parent;
updates.push({
start: node.start,
offset: node.end - node.start,
stream: st()
});
}
});
updates.sort(function(a, b) { return a.start - b.start; });
(function next () {
if (updates.length === 0) return done();
var s = updates.shift();
output.write(src.slice(pos, s.start));
pos = s.start + s.offset;
s.stream.pipe(output, { end: false });
if (opts.sourceMap) {
s.stream.pipe(concat({ encoding: 'string' }, function (chunk) {
// We have to give magic-string the replacement string,
// so it can calculate the amount of lines and columns.
if (s.offset === 0) {
sourcemapper.appendRight(s.start, chunk);
} else {
sourcemapper.overwrite(s.start, s.start + s.offset, chunk);
}
})).on('finish', next);
} else {
s.stream.on('end', next);
}
})();
function done () {
output.write(src.slice(pos));
if (opts.sourceMap) {
var map = sourcemapper.generateMap({
source: opts.inputFilename || 'input.js',
includeContent: true
});
if (inputMap) {
var merged = mergeSourceMap(inputMap, map);
output.write('\n' + convertSourceMap.fromObject(merged).toComment() + '\n');
} else {
output.write('\n//# sourceMappingURL=' + map.toUrl() + '\n');
}
}
output.end();
}
}
function error (msg) {
var err = typeof msg === 'string' ? new Error(msg) : msg;
output.emit('error', err);
}
function walk (node) {
if (opts.sourceMap) {
sourcemapper.addSourcemapLocation(node.start);
sourcemapper.addSourcemapLocation(node.end);
}
var isreq = isRequire(node);
var isreqm = false, isreqv = false, reqid;
if (isreq) {
reqid = node.arguments[0].value;
isreqm = has(modules, reqid);
isreqv = has(varModules, reqid);
}
if (isreqv && node.parent.type === 'VariableDeclarator'
&& node.parent.id.type === 'Identifier') {
var binding = scan.getBinding(node.parent.id);
if (binding) binding.value = varModules[reqid];
}
else if (isreqv && node.parent.type === 'AssignmentExpression'
&& node.parent.left.type === 'Identifier') {
var binding = scan.getBinding(node.parent.left);
if (binding) binding.value = varModules[reqid];
}
else if (isreqv && node.parent.type === 'MemberExpression'
&& isStaticProperty(node.parent.property)
&& node.parent.parent.type === 'VariableDeclarator'
&& node.parent.parent.id.type === 'Identifier') {
var binding = scan.getBinding(node.parent.parent.id);
var v = varModules[reqid][resolveProperty(node.parent.property)];
if (binding) binding.value = v;
}
else if (isreqv && node.parent.type === 'MemberExpression'
&& node.parent.property.type === 'Identifier') {
//vars[node.parent.parent.id.name] = varModules[reqid];
}
else if (isreqv && node.parent.type === 'CallExpression') {
//
}
if (isreqm && node.parent.type === 'VariableDeclarator'
&& node.parent.id.type === 'Identifier') {
var binding = scan.getBinding(node.parent.id);
if (binding) {
binding.module = modules[reqid];
binding.initializer = node.parent;
binding.remove(node.parent.id);
moduleBindings.push(binding);
}
}
else if (isreqm && node.parent.type === 'AssignmentExpression'
&& node.parent.left.type === 'Identifier') {
var binding = scan.getBinding(node.parent.left);
if (binding) {
binding.module = modules[reqid];
binding.initializer = node.parent;
binding.remove(node.parent.left);
moduleBindings.push(binding);
}
}
else if (isreqm && node.parent.type === 'MemberExpression'
&& isStaticProperty(node.parent.property)
&& node.parent.parent.type === 'VariableDeclarator'
&& node.parent.parent.id.type === 'Identifier') {
var binding = scan.getBinding(node.parent.parent.id);
if (binding) {
binding.module = modules[reqid][resolveProperty(node.parent.property)];
binding.initializer = node.parent.parent;
binding.remove(node.parent.parent.id);
moduleBindings.push(binding);
}
}
else if (isreqm && node.parent.type === 'MemberExpression'
&& isStaticProperty(node.parent.property)) {
var name = resolveProperty(node.parent.property);
var cur = copy(node.parent.parent);
cur.callee = copy(node.parent.property);
cur.callee.parent = cur;
traverse(cur.callee, modules[reqid][name]);
}
else if (isreqm && node.parent.type === 'CallExpression') {
var cur = copy(node.parent);
var iname = Math.pow(16,8) * Math.random();
cur.callee = {
type: 'Identifier',
name: '_' + Math.floor(iname).toString(16),
parent: cur
};
traverse(cur.callee, modules[reqid]);
}
if (node.type === 'Identifier') {
var binding = scan.getBinding(node)
if (binding && binding.module) traverse(node, binding.module, binding);
}
}
function traverse (node, val, binding) {
for (var p = node; p; p = p.parent) {
if (p.start === undefined || p.end === undefined) continue;
}
if (node.parent.type === 'CallExpression') {
if (typeof val !== 'function') {
return error(
'tried to statically call ' + inspect(val)
+ ' as a function'
);
}
var xvars = getVars(node.parent, vars);
xvars[node.name] = val;
var res = evaluate(node.parent, xvars);
if (res !== undefined) {
if (binding) binding.remove(node)
updates.push({
start: node.parent.start,
offset: node.parent.end - node.parent.start,
stream: isStream(res) ? wrapStream(res) : st(String(res))
});
}
}
else if (node.parent.type === 'MemberExpression') {
if (!isStaticProperty(node.parent.property)) {
return error(
'dynamic property in member expression: '
+ body.slice(node.parent.start, node.parent.end)
);
}
var cur = node.parent.parent;
if (cur.type === 'MemberExpression') {
cur = cur.parent;
if (cur.type !== 'CallExpression'
&& cur.parent.type === 'CallExpression') {
cur = cur.parent;
}
}
if (node.parent.type === 'MemberExpression'
&& (cur.type !== 'CallExpression'
&& cur.type !== 'MemberExpression')) {
cur = node.parent;
}
var xvars = getVars(cur, vars);
xvars[node.name] = val;
var res = evaluate(cur, xvars);
if (res === undefined && cur.type === 'CallExpression') {
// static-eval can't safely evaluate code with callbacks, so do it manually in a safe way
var callee = evaluate(cur.callee, xvars);
var args = cur.arguments.map(function (arg) {
// Return a function stub for callbacks so that `static-module` users
// can do `callback.toString()` and get the original source
if (arg.type === 'FunctionExpression' || arg.type === 'ArrowFunctionExpression') {
var fn = function () {
throw new Error('static-module: cannot call callbacks defined inside source code');
};
fn.toString = function () {
return body.slice(arg.start, arg.end);
};
return fn;
}
return evaluate(arg, xvars);
});
if (callee !== undefined) {
try {
res = callee.apply(null, args);
} catch (err) {
// Evaluate to undefined
}
}
}
if (res !== undefined) {
if (binding) binding.remove(node)
updates.push({
start: cur.start,
offset: cur.end - cur.start,
stream: isStream(res) ? wrapStream(res) : st(String(res))
});
}
}
else if (node.parent.type === 'UnaryExpression') {
var xvars = getVars(node.parent, vars);
xvars[node.name] = val;
var res = evaluate(node.parent, xvars);
if (res !== undefined) {
if (binding) binding.remove(node)
updates.push({
start: node.parent.start,
offset: node.parent.end - node.parent.start,
stream: isStream(res) ? wrapStream(res) : st(String(res))
});
} else {
output.emit('error', new Error(
'unsupported unary operator: ' + node.parent.operator
));
}
}
else {
output.emit('error', new Error(
'unsupported type for static module: ' + node.parent.type
+ '\nat expression:\n\n ' + unparse(node.parent) + '\n'
));
}
}
}
function isRequire (node) {
var c = node.callee;
return c
&& node.type === 'CallExpression'
&& c.type === 'Identifier'
&& c.name === 'require'
;
}
function isStream (s) {
return s && typeof s === 'object' && typeof s.pipe === 'function';
}
function wrapStream (s) {
if (typeof s.read === 'function') return s
else return (new Readable).wrap(s)
}
function isStaticProperty(node) {
return node.type === 'Identifier' || node.type === 'Literal';
}
function resolveProperty(node) {
return node.type === 'Identifier' ? node.name : node.value;
}
function st (msg) {
var r = new Readable;
r._read = function () {};
if (msg != null) r.push(msg);
r.push(null);
return r;
}
function nearestScope(node) {
do {
var scope = scan.scope(node);
if (scope) return scope;
} while ((node = node.parent));
}
function getVars(node, vars) {
var xvars = copy(vars || {});
var scope = nearestScope(node);
if (scope) {
scope.forEachAvailable(function (binding, name) {
if (binding.hasOwnProperty('value')) xvars[name] = binding.value;
});
}
return xvars;
}