You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
290 lines
7.5 KiB
290 lines
7.5 KiB
"use strict"; |
|
|
|
Object.defineProperty(exports, "__esModule", { |
|
value: true |
|
}); |
|
exports.default = void 0; |
|
exports.ensure = ensure; |
|
exports.get = get; |
|
exports.getDependencies = getDependencies; |
|
exports.list = void 0; |
|
exports.minVersion = minVersion; |
|
|
|
var _traverse = require("@babel/traverse"); |
|
|
|
var _t = require("@babel/types"); |
|
|
|
var _helpers = require("./helpers"); |
|
|
|
const { |
|
assignmentExpression, |
|
cloneNode, |
|
expressionStatement, |
|
file, |
|
identifier |
|
} = _t; |
|
|
|
function makePath(path) { |
|
const parts = []; |
|
|
|
for (; path.parentPath; path = path.parentPath) { |
|
parts.push(path.key); |
|
if (path.inList) parts.push(path.listKey); |
|
} |
|
|
|
return parts.reverse().join("."); |
|
} |
|
|
|
let FileClass = undefined; |
|
|
|
function getHelperMetadata(file) { |
|
const globals = new Set(); |
|
const localBindingNames = new Set(); |
|
const dependencies = new Map(); |
|
let exportName; |
|
let exportPath; |
|
const exportBindingAssignments = []; |
|
const importPaths = []; |
|
const importBindingsReferences = []; |
|
const dependencyVisitor = { |
|
ImportDeclaration(child) { |
|
const name = child.node.source.value; |
|
|
|
if (!_helpers.default[name]) { |
|
throw child.buildCodeFrameError(`Unknown helper ${name}`); |
|
} |
|
|
|
if (child.get("specifiers").length !== 1 || !child.get("specifiers.0").isImportDefaultSpecifier()) { |
|
throw child.buildCodeFrameError("Helpers can only import a default value"); |
|
} |
|
|
|
const bindingIdentifier = child.node.specifiers[0].local; |
|
dependencies.set(bindingIdentifier, name); |
|
importPaths.push(makePath(child)); |
|
}, |
|
|
|
ExportDefaultDeclaration(child) { |
|
const decl = child.get("declaration"); |
|
|
|
if (!decl.isFunctionDeclaration() || !decl.node.id) { |
|
throw decl.buildCodeFrameError("Helpers can only export named function declarations"); |
|
} |
|
|
|
exportName = decl.node.id.name; |
|
exportPath = makePath(child); |
|
}, |
|
|
|
ExportAllDeclaration(child) { |
|
throw child.buildCodeFrameError("Helpers can only export default"); |
|
}, |
|
|
|
ExportNamedDeclaration(child) { |
|
throw child.buildCodeFrameError("Helpers can only export default"); |
|
}, |
|
|
|
Statement(child) { |
|
if (child.isModuleDeclaration()) return; |
|
child.skip(); |
|
} |
|
|
|
}; |
|
const referenceVisitor = { |
|
Program(path) { |
|
const bindings = path.scope.getAllBindings(); |
|
Object.keys(bindings).forEach(name => { |
|
if (name === exportName) return; |
|
if (dependencies.has(bindings[name].identifier)) return; |
|
localBindingNames.add(name); |
|
}); |
|
}, |
|
|
|
ReferencedIdentifier(child) { |
|
const name = child.node.name; |
|
const binding = child.scope.getBinding(name); |
|
|
|
if (!binding) { |
|
globals.add(name); |
|
} else if (dependencies.has(binding.identifier)) { |
|
importBindingsReferences.push(makePath(child)); |
|
} |
|
}, |
|
|
|
AssignmentExpression(child) { |
|
const left = child.get("left"); |
|
if (!(exportName in left.getBindingIdentifiers())) return; |
|
|
|
if (!left.isIdentifier()) { |
|
throw left.buildCodeFrameError("Only simple assignments to exports are allowed in helpers"); |
|
} |
|
|
|
const binding = child.scope.getBinding(exportName); |
|
|
|
if (binding != null && binding.scope.path.isProgram()) { |
|
exportBindingAssignments.push(makePath(child)); |
|
} |
|
} |
|
|
|
}; |
|
(0, _traverse.default)(file.ast, dependencyVisitor, file.scope); |
|
(0, _traverse.default)(file.ast, referenceVisitor, file.scope); |
|
if (!exportPath) throw new Error("Helpers must have a default export."); |
|
exportBindingAssignments.reverse(); |
|
return { |
|
globals: Array.from(globals), |
|
localBindingNames: Array.from(localBindingNames), |
|
dependencies, |
|
exportBindingAssignments, |
|
exportPath, |
|
exportName, |
|
importBindingsReferences, |
|
importPaths |
|
}; |
|
} |
|
|
|
function permuteHelperAST(file, metadata, id, localBindings, getDependency) { |
|
if (localBindings && !id) { |
|
throw new Error("Unexpected local bindings for module-based helpers."); |
|
} |
|
|
|
if (!id) return; |
|
const { |
|
localBindingNames, |
|
dependencies, |
|
exportBindingAssignments, |
|
exportPath, |
|
exportName, |
|
importBindingsReferences, |
|
importPaths |
|
} = metadata; |
|
const dependenciesRefs = {}; |
|
dependencies.forEach((name, id) => { |
|
dependenciesRefs[id.name] = typeof getDependency === "function" && getDependency(name) || id; |
|
}); |
|
const toRename = {}; |
|
const bindings = new Set(localBindings || []); |
|
localBindingNames.forEach(name => { |
|
let newName = name; |
|
|
|
while (bindings.has(newName)) newName = "_" + newName; |
|
|
|
if (newName !== name) toRename[name] = newName; |
|
}); |
|
|
|
if (id.type === "Identifier" && exportName !== id.name) { |
|
toRename[exportName] = id.name; |
|
} |
|
|
|
const { |
|
path |
|
} = file; |
|
const exp = path.get(exportPath); |
|
const imps = importPaths.map(p => path.get(p)); |
|
const impsBindingRefs = importBindingsReferences.map(p => path.get(p)); |
|
const decl = exp.get("declaration"); |
|
|
|
if (id.type === "Identifier") { |
|
exp.replaceWith(decl); |
|
} else if (id.type === "MemberExpression") { |
|
exportBindingAssignments.forEach(assignPath => { |
|
const assign = path.get(assignPath); |
|
assign.replaceWith(assignmentExpression("=", id, assign.node)); |
|
}); |
|
exp.replaceWith(decl); |
|
path.pushContainer("body", expressionStatement(assignmentExpression("=", id, identifier(exportName)))); |
|
} else { |
|
throw new Error("Unexpected helper format."); |
|
} |
|
|
|
Object.keys(toRename).forEach(name => { |
|
path.scope.rename(name, toRename[name]); |
|
}); |
|
|
|
for (const path of imps) path.remove(); |
|
|
|
for (const path of impsBindingRefs) { |
|
const node = cloneNode(dependenciesRefs[path.node.name]); |
|
path.replaceWith(node); |
|
} |
|
} |
|
|
|
const helperData = Object.create(null); |
|
|
|
function loadHelper(name) { |
|
if (!helperData[name]) { |
|
const helper = _helpers.default[name]; |
|
|
|
if (!helper) { |
|
throw Object.assign(new ReferenceError(`Unknown helper ${name}`), { |
|
code: "BABEL_HELPER_UNKNOWN", |
|
helper: name |
|
}); |
|
} |
|
|
|
const fn = () => { |
|
{ |
|
if (!FileClass) { |
|
const fakeFile = { |
|
ast: file(helper.ast()), |
|
path: null |
|
}; |
|
(0, _traverse.default)(fakeFile.ast, { |
|
Program: path => (fakeFile.path = path).stop() |
|
}); |
|
return fakeFile; |
|
} |
|
} |
|
return new FileClass({ |
|
filename: `babel-helper://${name}` |
|
}, { |
|
ast: file(helper.ast()), |
|
code: "[internal Babel helper code]", |
|
inputMap: null |
|
}); |
|
}; |
|
|
|
let metadata = null; |
|
helperData[name] = { |
|
minVersion: helper.minVersion, |
|
|
|
build(getDependency, id, localBindings) { |
|
const file = fn(); |
|
metadata || (metadata = getHelperMetadata(file)); |
|
permuteHelperAST(file, metadata, id, localBindings, getDependency); |
|
return { |
|
nodes: file.ast.program.body, |
|
globals: metadata.globals |
|
}; |
|
}, |
|
|
|
getDependencies() { |
|
metadata || (metadata = getHelperMetadata(fn())); |
|
return Array.from(metadata.dependencies.values()); |
|
} |
|
|
|
}; |
|
} |
|
|
|
return helperData[name]; |
|
} |
|
|
|
function get(name, getDependency, id, localBindings) { |
|
return loadHelper(name).build(getDependency, id, localBindings); |
|
} |
|
|
|
function minVersion(name) { |
|
return loadHelper(name).minVersion; |
|
} |
|
|
|
function getDependencies(name) { |
|
return loadHelper(name).getDependencies(); |
|
} |
|
|
|
function ensure(name, newFileClass) { |
|
FileClass || (FileClass = newFileClass); |
|
loadHelper(name); |
|
} |
|
|
|
const list = Object.keys(_helpers.default).map(name => name.replace(/^_/, "")); |
|
exports.list = list; |
|
var _default = get; |
|
exports.default = _default; |