mirror of
https://github.com/soconnor0919/beenpad.git
synced 2026-02-05 00:06:40 -05:00
346 lines
12 KiB
JavaScript
346 lines
12 KiB
JavaScript
import MagicString from 'magic-string';
|
|
import { walk } from 'estree-walker';
|
|
|
|
const isNodeInPatternWeakSet = new WeakSet();
|
|
function setIsNodeInPattern(node) {
|
|
return isNodeInPatternWeakSet.add(node);
|
|
}
|
|
function isNodeInPattern(node) {
|
|
return isNodeInPatternWeakSet.has(node);
|
|
}
|
|
/**
|
|
* Same logic from \@vue/compiler-core & \@vue/compiler-sfc
|
|
* Except this is using acorn AST
|
|
*/
|
|
function esmWalker(root, { onIdentifier, onImportMeta, onDynamicImport, onCallExpression }) {
|
|
const parentStack = [];
|
|
const varKindStack = [];
|
|
const scopeMap = new WeakMap();
|
|
const identifiers = [];
|
|
const setScope = (node, name) => {
|
|
let scopeIds = scopeMap.get(node);
|
|
if (scopeIds && scopeIds.has(name)) {
|
|
return;
|
|
}
|
|
if (!scopeIds) {
|
|
scopeIds = new Set();
|
|
scopeMap.set(node, scopeIds);
|
|
}
|
|
scopeIds.add(name);
|
|
};
|
|
function isInScope(name, parents) {
|
|
return parents.some((node) => {
|
|
var _scopeMap$get;
|
|
return node && ((_scopeMap$get = scopeMap.get(node)) === null || _scopeMap$get === void 0 ? void 0 : _scopeMap$get.has(name));
|
|
});
|
|
}
|
|
function handlePattern(p, parentScope) {
|
|
if (p.type === "Identifier") {
|
|
setScope(parentScope, p.name);
|
|
} else if (p.type === "RestElement") {
|
|
handlePattern(p.argument, parentScope);
|
|
} else if (p.type === "ObjectPattern") {
|
|
p.properties.forEach((property) => {
|
|
if (property.type === "RestElement") {
|
|
setScope(parentScope, property.argument.name);
|
|
} else {
|
|
handlePattern(property.value, parentScope);
|
|
}
|
|
});
|
|
} else if (p.type === "ArrayPattern") {
|
|
p.elements.forEach((element) => {
|
|
if (element) {
|
|
handlePattern(element, parentScope);
|
|
}
|
|
});
|
|
} else if (p.type === "AssignmentPattern") {
|
|
handlePattern(p.left, parentScope);
|
|
} else {
|
|
setScope(parentScope, p.name);
|
|
}
|
|
}
|
|
walk(root, {
|
|
enter(node, parent) {
|
|
if (node.type === "ImportDeclaration") {
|
|
return this.skip();
|
|
}
|
|
// track parent stack, skip for "else-if"/"else" branches as acorn nests
|
|
// the ast within "if" nodes instead of flattening them
|
|
if (parent && !(parent.type === "IfStatement" && node === parent.alternate)) {
|
|
parentStack.unshift(parent);
|
|
}
|
|
// track variable declaration kind stack used by VariableDeclarator
|
|
if (node.type === "VariableDeclaration") {
|
|
varKindStack.unshift(node.kind);
|
|
}
|
|
if (node.type === "CallExpression") {
|
|
onCallExpression === null || onCallExpression === void 0 ? void 0 : onCallExpression(node);
|
|
}
|
|
if (node.type === "MetaProperty" && node.meta.name === "import") {
|
|
onImportMeta === null || onImportMeta === void 0 ? void 0 : onImportMeta(node);
|
|
} else if (node.type === "ImportExpression") {
|
|
onDynamicImport === null || onDynamicImport === void 0 ? void 0 : onDynamicImport(node);
|
|
}
|
|
if (node.type === "Identifier") {
|
|
if (!isInScope(node.name, parentStack) && isRefIdentifier(node, parent, parentStack)) {
|
|
// record the identifier, for DFS -> BFS
|
|
identifiers.push([node, parentStack.slice(0)]);
|
|
}
|
|
} else if (isFunctionNode(node)) {
|
|
// If it is a function declaration, it could be shadowing an import
|
|
// Add its name to the scope so it won't get replaced
|
|
if (node.type === "FunctionDeclaration") {
|
|
const parentScope = findParentScope(parentStack);
|
|
if (parentScope) {
|
|
setScope(parentScope, node.id.name);
|
|
}
|
|
}
|
|
// walk function expressions and add its arguments to known identifiers
|
|
// so that we don't prefix them
|
|
node.params.forEach((p) => {
|
|
if (p.type === "ObjectPattern" || p.type === "ArrayPattern") {
|
|
handlePattern(p, node);
|
|
return;
|
|
}
|
|
walk(p.type === "AssignmentPattern" ? p.left : p, { enter(child, parent) {
|
|
// skip params default value of destructure
|
|
if ((parent === null || parent === void 0 ? void 0 : parent.type) === "AssignmentPattern" && (parent === null || parent === void 0 ? void 0 : parent.right) === child) {
|
|
return this.skip();
|
|
}
|
|
if (child.type !== "Identifier") {
|
|
return;
|
|
}
|
|
// do not record as scope variable if is a destructuring keyword
|
|
if (isStaticPropertyKey(child, parent)) {
|
|
return;
|
|
}
|
|
// do not record if this is a default value
|
|
// assignment of a destructuring variable
|
|
if ((parent === null || parent === void 0 ? void 0 : parent.type) === "TemplateLiteral" && (parent === null || parent === void 0 ? void 0 : parent.expressions.includes(child)) || (parent === null || parent === void 0 ? void 0 : parent.type) === "CallExpression" && (parent === null || parent === void 0 ? void 0 : parent.callee) === child) {
|
|
return;
|
|
}
|
|
setScope(node, child.name);
|
|
} });
|
|
});
|
|
} else if (node.type === "Property" && parent.type === "ObjectPattern") {
|
|
// mark property in destructuring pattern
|
|
setIsNodeInPattern(node);
|
|
} else if (node.type === "VariableDeclarator") {
|
|
const parentFunction = findParentScope(parentStack, varKindStack[0] === "var");
|
|
if (parentFunction) {
|
|
handlePattern(node.id, parentFunction);
|
|
}
|
|
} else if (node.type === "CatchClause" && node.param) {
|
|
handlePattern(node.param, node);
|
|
}
|
|
},
|
|
leave(node, parent) {
|
|
// untrack parent stack from above
|
|
if (parent && !(parent.type === "IfStatement" && node === parent.alternate)) {
|
|
parentStack.shift();
|
|
}
|
|
if (node.type === "VariableDeclaration") {
|
|
varKindStack.shift();
|
|
}
|
|
}
|
|
});
|
|
// emit the identifier events in BFS so the hoisted declarations
|
|
// can be captured correctly
|
|
identifiers.forEach(([node, stack]) => {
|
|
if (!isInScope(node.name, stack)) {
|
|
const parent = stack[0];
|
|
const grandparent = stack[1];
|
|
const hasBindingShortcut = isStaticProperty(parent) && parent.shorthand && (!isNodeInPattern(parent) || isInDestructuringAssignment(parent, parentStack));
|
|
const classDeclaration = parent.type === "PropertyDefinition" && (grandparent === null || grandparent === void 0 ? void 0 : grandparent.type) === "ClassBody" || parent.type === "ClassDeclaration" && node === parent.superClass;
|
|
const classExpression = parent.type === "ClassExpression" && node === parent.id;
|
|
onIdentifier === null || onIdentifier === void 0 ? void 0 : onIdentifier(node, {
|
|
hasBindingShortcut,
|
|
classDeclaration,
|
|
classExpression
|
|
}, stack);
|
|
}
|
|
});
|
|
}
|
|
function isRefIdentifier(id, parent, parentStack) {
|
|
// declaration id
|
|
if (parent.type === "CatchClause" || (parent.type === "VariableDeclarator" || parent.type === "ClassDeclaration") && parent.id === id) {
|
|
return false;
|
|
}
|
|
if (isFunctionNode(parent)) {
|
|
// function declaration/expression id
|
|
if (parent.id === id) {
|
|
return false;
|
|
}
|
|
// params list
|
|
if (parent.params.includes(id)) {
|
|
return false;
|
|
}
|
|
}
|
|
// class method name
|
|
if (parent.type === "MethodDefinition" && !parent.computed) {
|
|
return false;
|
|
}
|
|
// property key
|
|
if (isStaticPropertyKey(id, parent)) {
|
|
return false;
|
|
}
|
|
// object destructuring pattern
|
|
if (isNodeInPattern(parent) && parent.value === id) {
|
|
return false;
|
|
}
|
|
// non-assignment array destructuring pattern
|
|
if (parent.type === "ArrayPattern" && !isInDestructuringAssignment(parent, parentStack)) {
|
|
return false;
|
|
}
|
|
// member expression property
|
|
if (parent.type === "MemberExpression" && parent.property === id && !parent.computed) {
|
|
return false;
|
|
}
|
|
if (parent.type === "ExportSpecifier") {
|
|
return false;
|
|
}
|
|
// is a special keyword but parsed as identifier
|
|
if (id.name === "arguments") {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
function isStaticProperty(node) {
|
|
return node && node.type === "Property" && !node.computed;
|
|
}
|
|
function isStaticPropertyKey(node, parent) {
|
|
return isStaticProperty(parent) && parent.key === node;
|
|
}
|
|
const functionNodeTypeRE = /Function(?:Expression|Declaration)$|Method$/;
|
|
function isFunctionNode(node) {
|
|
return functionNodeTypeRE.test(node.type);
|
|
}
|
|
const blockNodeTypeRE = /^BlockStatement$|^For(?:In|Of)?Statement$/;
|
|
function isBlock(node) {
|
|
return blockNodeTypeRE.test(node.type);
|
|
}
|
|
function findParentScope(parentStack, isVar = false) {
|
|
return parentStack.find(isVar ? isFunctionNode : isBlock);
|
|
}
|
|
function isInDestructuringAssignment(parent, parentStack) {
|
|
if (parent && (parent.type === "Property" || parent.type === "ArrayPattern")) {
|
|
return parentStack.some((i) => i.type === "AssignmentExpression");
|
|
}
|
|
return false;
|
|
}
|
|
function getArbitraryModuleIdentifier(node) {
|
|
return node.type === "Identifier" ? node.name : node.raw;
|
|
}
|
|
|
|
// TODO: better source map replacement
|
|
function automockModule(code, mockType, parse, options = {}) {
|
|
const globalThisAccessor = options.globalThisAccessor || "\"__vitest_mocker__\"";
|
|
const ast = parse(code);
|
|
const m = new MagicString(code);
|
|
const allSpecifiers = [];
|
|
let importIndex = 0;
|
|
for (const _node of ast.body) {
|
|
if (_node.type === "ExportAllDeclaration") {
|
|
throw new Error(`automocking files with \`export *\` is not supported in browser mode because it cannot be statically analysed`);
|
|
}
|
|
if (_node.type === "ExportNamedDeclaration") {
|
|
const node = _node;
|
|
const declaration = node.declaration;
|
|
function traversePattern(expression) {
|
|
// export const test = '1'
|
|
if (expression.type === "Identifier") {
|
|
allSpecifiers.push({ name: expression.name });
|
|
} else if (expression.type === "ArrayPattern") {
|
|
expression.elements.forEach((element) => {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
traversePattern(element);
|
|
});
|
|
} else if (expression.type === "ObjectPattern") {
|
|
expression.properties.forEach((property) => {
|
|
// export const { ...rest } = {}
|
|
if (property.type === "RestElement") {
|
|
traversePattern(property);
|
|
} else if (property.type === "Property") {
|
|
traversePattern(property.value);
|
|
} else ;
|
|
});
|
|
} else if (expression.type === "RestElement") {
|
|
traversePattern(expression.argument);
|
|
} else if (expression.type === "AssignmentPattern") {
|
|
throw new Error(`AssignmentPattern is not supported. Please open a new bug report.`);
|
|
} else if (expression.type === "MemberExpression") {
|
|
throw new Error(`MemberExpression is not supported. Please open a new bug report.`);
|
|
} else ;
|
|
}
|
|
if (declaration) {
|
|
if (declaration.type === "FunctionDeclaration") {
|
|
allSpecifiers.push({ name: declaration.id.name });
|
|
} else if (declaration.type === "VariableDeclaration") {
|
|
declaration.declarations.forEach((declaration) => {
|
|
traversePattern(declaration.id);
|
|
});
|
|
} else if (declaration.type === "ClassDeclaration") {
|
|
allSpecifiers.push({ name: declaration.id.name });
|
|
} else ;
|
|
m.remove(node.start, declaration.start);
|
|
}
|
|
const specifiers = node.specifiers || [];
|
|
const source = node.source;
|
|
if (!source && specifiers.length) {
|
|
specifiers.forEach((specifier) => {
|
|
allSpecifiers.push({
|
|
alias: getArbitraryModuleIdentifier(specifier.exported),
|
|
name: getArbitraryModuleIdentifier(specifier.local)
|
|
});
|
|
});
|
|
m.remove(node.start, node.end);
|
|
} else if (source && specifiers.length) {
|
|
const importNames = [];
|
|
specifiers.forEach((specifier) => {
|
|
const importedName = `__vitest_imported_${importIndex++}__`;
|
|
importNames.push([getArbitraryModuleIdentifier(specifier.local), importedName]);
|
|
allSpecifiers.push({
|
|
name: importedName,
|
|
alias: getArbitraryModuleIdentifier(specifier.exported)
|
|
});
|
|
});
|
|
const importString = `import { ${importNames.map(([name, alias]) => `${name} as ${alias}`).join(", ")} } from '${source.value}'`;
|
|
m.overwrite(node.start, node.end, importString);
|
|
}
|
|
}
|
|
if (_node.type === "ExportDefaultDeclaration") {
|
|
const node = _node;
|
|
const declaration = node.declaration;
|
|
allSpecifiers.push({
|
|
name: "__vitest_default",
|
|
alias: "default"
|
|
});
|
|
m.overwrite(node.start, declaration.start, `const __vitest_default = `);
|
|
}
|
|
}
|
|
const moduleObject = `
|
|
const __vitest_current_es_module__ = {
|
|
__esModule: true,
|
|
${allSpecifiers.map(({ name }) => `["${name}"]: ${name},`).join("\n ")}
|
|
}
|
|
const __vitest_mocked_module__ = globalThis[${globalThisAccessor}].mockObject(__vitest_current_es_module__, "${mockType}")
|
|
`;
|
|
const assigning = allSpecifiers.map(({ name }, index) => {
|
|
return `const __vitest_mocked_${index}__ = __vitest_mocked_module__["${name}"]`;
|
|
}).join("\n");
|
|
const redeclarations = allSpecifiers.map(({ name, alias }, index) => {
|
|
return ` __vitest_mocked_${index}__ as ${alias || name},`;
|
|
}).join("\n");
|
|
const specifiersExports = `
|
|
export {
|
|
${redeclarations}
|
|
}
|
|
`;
|
|
m.append(moduleObject + assigning + specifiersExports);
|
|
return m;
|
|
}
|
|
|
|
export { automockModule as a, esmWalker as e };
|