cjs-es
Version:
Transform CommonJS module into ES module.
384 lines (356 loc) • 9.05 kB
JavaScript
const {attachScopes} = require("@rollup/pluginutils");
const RX_DEFAULT = /.*?\/\/.*\bdefault\b/y;
function createTopLevelAnalyzer() {
const nodes = [];
let parent;
return {enter, get, isTop, isTopChild};
function enter(node, _parent) {
parent = _parent;
if (parent && parent.type === "Program") {
node.topLevel = true;
nodes.push(node);
}
}
function get() {
return nodes[nodes.length - 1];
}
function isTop() {
return !parent || parent.type === "Program";
}
function isTopChild() {
return parent && parent.topLevel;
}
}
function createScopeAnalyzer(ast, dummy) {
let scope = dummy ? {} : attachScopes(ast, "scope");
return {enter, leave, has, findFunction, setMeta, getMeta};
function enter(node) {
if (node.scope) {
scope = node.scope;
scope._node = node;
}
}
function leave(node) {
if (node.scope) {
scope = node.scope.parent;
}
}
function has(name) {
return scope.contains && scope.contains(name);
}
function findFunction() {
// exclude arrow functions
let node = scope._node;
while (node) {
if (node.type === "FunctionExpression" || node.type === "FunctionDeclaration") {
return node;
}
node = node.scope.parent && node.scope.parent._node;
}
return null;
}
function findDeclaredScope(name) {
if (dummy) {
return scope;
}
let declareScope = scope;
while (declareScope) {
if (declareScope.declarations[name]) {
break;
}
declareScope = declareScope.parent;
}
return declareScope;
}
function setMeta(varName, metaName, metaValue) {
const declaredScope = findDeclaredScope(varName);
if (!declaredScope) {
throw new Error(`'${varName}' is not defined`);
}
if (!declaredScope._metas) {
declaredScope._metas = {};
}
if (!declaredScope._metas[varName]) {
declaredScope._metas[varName] = {};
}
declaredScope._metas[varName][metaName] = metaValue;
}
function getMeta(varName, metaName) {
const declaredScope = findDeclaredScope(varName);
if (!declaredScope) {
return null;
}
if (!declaredScope._metas) {
return null;
}
if (!declaredScope._metas[varName]) {
return null;
}
return declaredScope._metas[varName][metaName];
}
}
function createAssignmentAnalyzer() {
return {enter};
function enter(node) {
if (node.type === "AssignmentExpression" || node.type === "AssignmentPattern") {
node.left.isAssignment = true;
} else if (node.type === "UpdateExpression") {
node.argument.isAssignment = true;
} else if (node.type === "ObjectPattern" && node.isAssignment) {
for (const prop of node.properties) {
prop.value.isAssignment = true;
}
} else if (node.type === "ArrayPattern" && node.isAssignment) {
for (const el of node.elements) {
if (el) {
el.isAssignment = true;
}
}
}
}
}
function getNestedExports(node) {
// extract export info from member expression.
if (node.type === "Identifier" && node.name === "exports") {
return {
node,
leftMost: node,
moduleExports: node
};
}
if (node.type !== "MemberExpression" || node.computed) {
return;
}
let isModule = false;
let isNamed = false;
if (node.object.name === "module" && node.property.name === "exports") {
// module.exports
isModule = true;
} else if (
node.object.type === "MemberExpression" &&
node.object.object.name === "module" &&
node.object.property.name === "exports"
) {
// module.exports.foo
isModule = true;
isNamed = true;
} else if (node.object.name === "exports") {
// exports.foo = ...
isNamed = true;
} else {
return;
}
return {
node,
name: isNamed ? node.property.name : undefined,
moduleExports: isModule ? (isNamed ? node.object : node) : undefined,
leftMost: isModule && isNamed ? node.object.object : node.object
};
}
function getLeftMost(node) {
while (node.type === "MemberExpression") {
node = node.object;
}
return node;
}
function getExportInfo(node) {
// extract export info from assignment expression
const exportInfo = getNestedExports(node.left);
if (!exportInfo) {
return;
}
return {
node: exportInfo.node,
assignExpression: node,
name: exportInfo.name,
leftMost: exportInfo.leftMost,
left: node.left,
key: node.left.property,
value: node.right,
object: !exportInfo.name && node.right.type === "ObjectExpression" && node.right.properties.length ?
getObjectInfo(node.right) : null,
required: node.right.type === "CallExpression" && getRequireInfo(node.right),
isIife: node.right.type === "CallExpression" && node.right.callee.type === "FunctionExpression"
};
}
function getDynamicImport(node) {
// CallExpression
if (
node.callee.type !== "MemberExpression" ||
node.callee.object.name !== "Promise" ||
node.callee.property.name !== "resolve"
) {
return;
}
if (
node.arguments.length !== 1 ||
node.arguments[0].type !== "CallExpression"
) {
return;
}
const required = getRequireInfo(node.arguments[0]);
if (required) {
return {
start: node.start,
end: node.end,
required
};
}
}
function getDeclareExport(node) {
if (node.declarations.length !== 1) {
return;
}
const dec = node.declarations[0];
if (dec.id.type !== "Identifier" || !dec.init || dec.init.type !== "AssignmentExpression") {
return;
}
const exported = getExportInfo(dec.init);
if (!exported) {
return;
}
return {
id: dec.id,
start: node.start,
end: node.end,
kind: node.kind,
exported
};
}
function getDeclareImport(node) {
const declarations = [];
for (let i = 0; i < node.declarations.length; i++) {
const dec = node.declarations[i];
if (!dec.init) {
continue;
}
let required;
let property;
if (dec.init.type === "CallExpression") {
// ... = require("...")
required = getRequireInfo(dec.init);
} else if (
// ... = require("...").foo
dec.init.type === "MemberExpression" &&
dec.init.object.type === "CallExpression" &&
dec.init.property.type === "Identifier"
) {
required = getRequireInfo(dec.init.object);
property = dec.init.property;
}
if (!required) {
continue;
}
let object;
if (!property && dec.id.type === "ObjectPattern") {
object = getObjectInfo(dec.id, true);
if (!object) {
continue;
}
} else if (dec.id.type !== "Identifier") {
continue;
}
declarations.push({
node: dec,
isSingleBinding: !object && !property,
object,
property,
left: dec.id,
right: dec.init,
required,
prev: i - 1 >= 0 ?
node.declarations[i - 1] : null,
next: i + 1 < node.declarations.length ?
node.declarations[i + 1] : null,
declaration: node
});
}
return declarations;
}
function getRequireInfo(node) {
if (
node.callee.name === "require" &&
node.arguments.length === 1 &&
node.arguments[0].type === "Literal"
) {
return {
node,
start: node.arguments[0].start,
end: node.arguments[0].end,
value: node.arguments[0].value,
};
}
}
function getObjectInfo(node, checkValueType) {
if (!node.properties.length) {
return;
}
const properties = [];
// property might be a require call
const requires = [];
for (const prop of node.properties) {
if (prop.key.type !== "Identifier" || prop.computed) {
return;
}
if (checkValueType && prop.value.type !== "Identifier") {
return;
}
if (prop.method) {
properties.push({
name: prop.key.name,
method: true,
generator: prop.value.generator,
key: prop.key,
value: prop.value
});
} else {
// note that if prop.shorthand == true then prop.key == prop.value
const required = prop.value.type === "CallExpression" &&
getRequireInfo(prop.value);
properties.push({
name: prop.key.name,
key: prop.key,
value: prop.value,
required
});
if (required) {
requires.push(required);
}
}
}
return {
start: node.start,
end: node.end,
properties,
requires
};
}
function hasDefaultComment(code, node) {
RX_DEFAULT.lastIndex = node.end;
return RX_DEFAULT.test(code);
}
function pathToName(s) {
return s.replace(/[\W_]/g, c => {
if (c == "/" || c == "\\") {
return "$";
}
if (c == "_") {
return "__";
}
return "_";
});
}
module.exports = {
createScopeAnalyzer,
createTopLevelAnalyzer,
createAssignmentAnalyzer,
getDeclareExport,
getDeclareImport,
getDynamicImport,
getLeftMost,
getNestedExports,
getExportInfo,
getRequireInfo,
hasDefaultComment,
pathToName
};