Files
2020-03-02 14:53:23 +07:00

340 lines
9.2 KiB
JavaScript

"use strict";
/* eslint-disable operator-linebreak */
/* eslint-disable prefer-const */
const common = require('../prelude/common.js');
const generate = require('escodegen').generate;
const babelParser = require('@babel/parser');
const parse = babelParser.parse;
const ALIAS_AS_RELATIVE = common.ALIAS_AS_RELATIVE;
const ALIAS_AS_RESOLVABLE = common.ALIAS_AS_RESOLVABLE;
function forge(pattern, was) {
return pattern.replace('{c1}', ', ').replace('{v1}', '"' + was.v1 + '"').replace('{c2}', was.v2 ? ', ' : '').replace('{v2}', was.v2 ? '"' + was.v2 + '"' : '').replace('{c3}', was.v3 ? ' from ' : '').replace('{v3}', was.v3 ? was.v3 : '');
}
function valid2(v2) {
return v2 === undefined || v2 === null || v2 === 'must-exclude' || v2 === 'may-exclude';
}
function visitor_REQUIRE_RESOLVE(n) {
// eslint-disable-line camelcase
const c = n.callee;
if (!c) return null;
const ci = c.object && c.object.type === 'Identifier' && c.object.name === 'require' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve';
if (!ci) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type === 'Literal';
if (!f) return null;
const m = n.arguments[1] && n.arguments[1].type === 'Literal';
return {
v1: n.arguments[0].value,
v2: m ? n.arguments[1].value : null
};
}
function visitor_REQUIRE(n) {
// eslint-disable-line camelcase
const c = n.callee;
if (!c) return null;
const ci = c.type === 'Identifier' && c.name === 'require';
if (!ci) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type === 'Literal';
if (!f) return null;
const m = n.arguments[1] && n.arguments[1].type === 'Literal';
return {
v1: n.arguments[0].value,
v2: m ? n.arguments[1].value : null
};
}
function visitor_IMPORT(n) {
// eslint-disable-line camelcase
const ni = n.type === 'ImportDeclaration';
if (!ni) return null;
const s = n.specifiers;
return {
v1: n.source.value,
v3: reconstructSpecifiers(s)
};
}
function visitor_PATH_JOIN(n) {
// eslint-disable-line camelcase
const c = n.callee;
if (!c) return null;
const ci = c.object && c.object.type === 'Identifier' && c.object.name === 'path' && c.property && c.property.type === 'Identifier' && c.property.name === 'join';
if (!ci) return null;
const dn = n.arguments[0] && n.arguments[0].type === 'Identifier' && n.arguments[0].name === '__dirname';
if (!dn) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[1] && n.arguments[1].type === 'Literal' && n.arguments.length === 2; // TODO concate them
if (!f) return null;
return {
v1: n.arguments[1].value
};
}
module.exports.visitor_SUCCESSFUL = function (node, test) {
// eslint-disable-line camelcase
let mustExclude, mayExclude, was;
was = visitor_REQUIRE_RESOLVE(node);
if (was) {
if (test) return forge('require.resolve({v1}{c2}{v2})', was);
if (!valid2(was.v2)) return null;
mustExclude = was.v2 === 'must-exclude';
mayExclude = was.v2 === 'may-exclude';
return {
alias: was.v1,
aliasType: ALIAS_AS_RESOLVABLE,
mustExclude: mustExclude,
mayExclude: mayExclude
};
}
was = visitor_REQUIRE(node);
if (was) {
if (test) return forge('require({v1}{c2}{v2})', was);
if (!valid2(was.v2)) return null;
mustExclude = was.v2 === 'must-exclude';
mayExclude = was.v2 === 'may-exclude';
return {
alias: was.v1,
aliasType: ALIAS_AS_RESOLVABLE,
mustExclude: mustExclude,
mayExclude: mayExclude
};
}
was = visitor_IMPORT(node);
if (was) {
if (test) return forge('import {v3}{c3}{v1}', was);
return {
alias: was.v1,
aliasType: ALIAS_AS_RESOLVABLE
};
}
was = visitor_PATH_JOIN(node);
if (was) {
if (test) return forge('path.join(__dirname{c1}{v1})', was);
return {
alias: was.v1,
aliasType: ALIAS_AS_RELATIVE,
mayExclude: false
};
}
return null;
};
function visitor_NONLITERAL(n) {
// eslint-disable-line camelcase
return function () {
const c = n.callee;
if (!c) return null;
const ci = c.object && c.object.type === 'Identifier' && c.object.name === 'require' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve';
if (!ci) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type !== 'Literal';
if (!f) return null;
const m = n.arguments[1];
if (!m) return {
v1: reconstruct(n.arguments[0])
};
const q = n.arguments[1] && n.arguments[1].type === 'Literal';
if (!q) return null;
return {
v1: reconstruct(n.arguments[0]),
v2: n.arguments[1].value
};
}() || function () {
const c = n.callee;
if (!c) return null;
const ci = c.type === 'Identifier' && c.name === 'require';
if (!ci) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[0] && n.arguments[0].type !== 'Literal';
if (!f) return null;
const m = n.arguments[1];
if (!m) return {
v1: reconstruct(n.arguments[0])
};
const q = n.arguments[1] && n.arguments[1].type === 'Literal';
if (!q) return null;
return {
v1: reconstruct(n.arguments[0]),
v2: n.arguments[1].value
};
}();
}
module.exports.visitor_NONLITERAL = function (node) {
// eslint-disable-line camelcase
let mustExclude, mayExclude, was;
was = visitor_NONLITERAL(node);
if (was) {
if (!valid2(was.v2)) return null;
mustExclude = was.v2 === 'must-exclude';
mayExclude = was.v2 === 'may-exclude';
return {
alias: was.v1,
mustExclude: mustExclude,
mayExclude: mayExclude
};
}
return null;
};
function visitor_MALFORMED(n) {
// eslint-disable-line camelcase
return function () {
const c = n.callee;
if (!c) return null;
const ci = c.object && c.object.type === 'Identifier' && c.object.name === 'require' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve';
if (!ci) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[0];
if (!f) return null;
return {
v1: reconstruct(n.arguments[0])
};
}() || function () {
const c = n.callee;
if (!c) return null;
const ci = c.type === 'Identifier' && c.name === 'require';
if (!ci) return null;
const f = n.type === 'CallExpression' && n.arguments && n.arguments[0];
if (!f) return null;
return {
v1: reconstruct(n.arguments[0])
};
}();
}
module.exports.visitor_MALFORMED = function (node) {
// eslint-disable-line camelcase
let was;
was = visitor_MALFORMED(node);
if (was) return {
alias: was.v1
};
return null;
};
function visitor_USESCWD(n) {
// eslint-disable-line camelcase
const c = n.callee;
if (!c) return null;
const ci = c.object && c.object.type === 'Identifier' && c.object.name === 'path' && c.property && c.property.type === 'Identifier' && c.property.name === 'resolve';
if (!ci) return null;
return {
v1: n.arguments.map(reconstruct).join(', ')
};
}
module.exports.visitor_USESCWD = function (node) {
// eslint-disable-line camelcase
let was;
was = visitor_USESCWD(node);
if (was) return {
alias: was.v1
};
return null;
};
function reconstructSpecifiers(specs) {
if (!specs || !specs.length) return '';
const defaults = [];
for (const spec of specs) {
if (spec.type === 'ImportDefaultSpecifier') {
defaults.push(spec.local.name);
}
}
const nonDefaults = [];
for (const spec of specs) {
if (spec.type === 'ImportSpecifier') {
if (spec.local.name === spec.imported.name) {
nonDefaults.push(spec.local.name);
} else {
nonDefaults.push(spec.imported.name + ' as ' + spec.local.name);
}
}
}
if (nonDefaults.length) {
defaults.push('{ ' + nonDefaults.join(', ') + ' }');
}
return defaults.join(', ');
}
function reconstruct(node) {
let v = generate(node).replace(/\n/g, '');
let v2;
while (true) {
v2 = v.replace(/\[ /g, '[').replace(/ \]/g, ']').replace(/ {2}/g, ' ');
if (v2 === v) break;
v = v2;
}
return v2;
}
function traverse(ast, visitor) {
// modified esprima-walk to support
// visitor return value and "trying" flag
let stack = [[ast, false]];
let i, j, key;
let len, item, node, trying, child;
for (i = 0; i < stack.length; i += 1) {
item = stack[i];
node = item[0];
if (node) {
trying = item[1] || node.type === 'TryStatement';
if (visitor(node, trying)) {
for (key in node) {
child = node[key];
if (child instanceof Array) {
len = child.length;
for (j = 0; j < len; j += 1) {
stack.push([child[j], trying]);
}
} else if (child && typeof child.type === 'string') {
stack.push([child, trying]);
}
}
}
}
}
}
module.exports.parse = function (body) {
return parse(body, {
allowImportExportEverywhere: true,
allowReturnOutsideFunction: true,
ecmaVersion: 8,
plugins: ['estree', 'bigInt']
});
};
module.exports.detect = function (body, visitor) {
const json = module.exports.parse(body);
if (!json) return;
traverse(json, visitor);
};