UNPKG

@vitest/mocker

Version:
775 lines (738 loc) • 28.3 kB
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'; import MagicString from 'magic-string'; import { e as esmWalker } from './chunk-automock.js'; import { a as relative } from './chunk-pathe.M-eThtNZ.js'; // AST walker module for ESTree compatible trees function makeTest(test) { if (typeof test === "string") { return function (type) { return type === test; } } else if (!test) { return function () { return true; } } else { return test } } var Found = function Found(node, state) { this.node = node; this.state = state; }; // Find the innermost node of a given type that contains the given // position. Interface similar to findNodeAt. function findNodeAround(node, pos, test, baseVisitor, state) { test = makeTest(test); if (!baseVisitor) { baseVisitor = base; } try { (function c(node, st, override) { var type = override || node.type; if (node.start > pos || node.end < pos) { return } visitNode(baseVisitor, type, node, st, c); if (test(type, node)) { throw new Found(node, st) } })(node, state); } catch (e) { if (e instanceof Found) { return e } throw e } } function skipThrough(node, st, c) { c(node, st); } function ignore(_node, _st, _c) {} function visitNode(baseVisitor, type, node, st, c) { if (baseVisitor[type] == null) { throw new Error(("No walker function defined for node type " + type)) } baseVisitor[type](node, st, c); } // Node walkers. var base = {}; base.Program = base.BlockStatement = base.StaticBlock = function (node, st, c) { for (var i = 0, list = node.body; i < list.length; i += 1) { var stmt = list[i]; c(stmt, st, "Statement"); } }; base.Statement = skipThrough; base.EmptyStatement = ignore; base.ExpressionStatement = base.ParenthesizedExpression = base.ChainExpression = function (node, st, c) { return c(node.expression, st, "Expression"); }; base.IfStatement = function (node, st, c) { c(node.test, st, "Expression"); c(node.consequent, st, "Statement"); if (node.alternate) { c(node.alternate, st, "Statement"); } }; base.LabeledStatement = function (node, st, c) { return c(node.body, st, "Statement"); }; base.BreakStatement = base.ContinueStatement = ignore; base.WithStatement = function (node, st, c) { c(node.object, st, "Expression"); c(node.body, st, "Statement"); }; base.SwitchStatement = function (node, st, c) { c(node.discriminant, st, "Expression"); for (var i = 0, list = node.cases; i < list.length; i += 1) { var cs = list[i]; c(cs, st); } }; base.SwitchCase = function (node, st, c) { if (node.test) { c(node.test, st, "Expression"); } for (var i = 0, list = node.consequent; i < list.length; i += 1) { var cons = list[i]; c(cons, st, "Statement"); } }; base.ReturnStatement = base.YieldExpression = base.AwaitExpression = function (node, st, c) { if (node.argument) { c(node.argument, st, "Expression"); } }; base.ThrowStatement = base.SpreadElement = function (node, st, c) { return c(node.argument, st, "Expression"); }; base.TryStatement = function (node, st, c) { c(node.block, st, "Statement"); if (node.handler) { c(node.handler, st); } if (node.finalizer) { c(node.finalizer, st, "Statement"); } }; base.CatchClause = function (node, st, c) { if (node.param) { c(node.param, st, "Pattern"); } c(node.body, st, "Statement"); }; base.WhileStatement = base.DoWhileStatement = function (node, st, c) { c(node.test, st, "Expression"); c(node.body, st, "Statement"); }; base.ForStatement = function (node, st, c) { if (node.init) { c(node.init, st, "ForInit"); } if (node.test) { c(node.test, st, "Expression"); } if (node.update) { c(node.update, st, "Expression"); } c(node.body, st, "Statement"); }; base.ForInStatement = base.ForOfStatement = function (node, st, c) { c(node.left, st, "ForInit"); c(node.right, st, "Expression"); c(node.body, st, "Statement"); }; base.ForInit = function (node, st, c) { if (node.type === "VariableDeclaration") { c(node, st); } else { c(node, st, "Expression"); } }; base.DebuggerStatement = ignore; base.FunctionDeclaration = function (node, st, c) { return c(node, st, "Function"); }; base.VariableDeclaration = function (node, st, c) { for (var i = 0, list = node.declarations; i < list.length; i += 1) { var decl = list[i]; c(decl, st); } }; base.VariableDeclarator = function (node, st, c) { c(node.id, st, "Pattern"); if (node.init) { c(node.init, st, "Expression"); } }; base.Function = function (node, st, c) { if (node.id) { c(node.id, st, "Pattern"); } for (var i = 0, list = node.params; i < list.length; i += 1) { var param = list[i]; c(param, st, "Pattern"); } c(node.body, st, node.expression ? "Expression" : "Statement"); }; base.Pattern = function (node, st, c) { if (node.type === "Identifier") { c(node, st, "VariablePattern"); } else if (node.type === "MemberExpression") { c(node, st, "MemberPattern"); } else { c(node, st); } }; base.VariablePattern = ignore; base.MemberPattern = skipThrough; base.RestElement = function (node, st, c) { return c(node.argument, st, "Pattern"); }; base.ArrayPattern = function (node, st, c) { for (var i = 0, list = node.elements; i < list.length; i += 1) { var elt = list[i]; if (elt) { c(elt, st, "Pattern"); } } }; base.ObjectPattern = function (node, st, c) { for (var i = 0, list = node.properties; i < list.length; i += 1) { var prop = list[i]; if (prop.type === "Property") { if (prop.computed) { c(prop.key, st, "Expression"); } c(prop.value, st, "Pattern"); } else if (prop.type === "RestElement") { c(prop.argument, st, "Pattern"); } } }; base.Expression = skipThrough; base.ThisExpression = base.Super = base.MetaProperty = ignore; base.ArrayExpression = function (node, st, c) { for (var i = 0, list = node.elements; i < list.length; i += 1) { var elt = list[i]; if (elt) { c(elt, st, "Expression"); } } }; base.ObjectExpression = function (node, st, c) { for (var i = 0, list = node.properties; i < list.length; i += 1) { var prop = list[i]; c(prop, st); } }; base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration; base.SequenceExpression = function (node, st, c) { for (var i = 0, list = node.expressions; i < list.length; i += 1) { var expr = list[i]; c(expr, st, "Expression"); } }; base.TemplateLiteral = function (node, st, c) { for (var i = 0, list = node.quasis; i < list.length; i += 1) { var quasi = list[i]; c(quasi, st); } for (var i$1 = 0, list$1 = node.expressions; i$1 < list$1.length; i$1 += 1) { var expr = list$1[i$1]; c(expr, st, "Expression"); } }; base.TemplateElement = ignore; base.UnaryExpression = base.UpdateExpression = function (node, st, c) { c(node.argument, st, "Expression"); }; base.BinaryExpression = base.LogicalExpression = function (node, st, c) { c(node.left, st, "Expression"); c(node.right, st, "Expression"); }; base.AssignmentExpression = base.AssignmentPattern = function (node, st, c) { c(node.left, st, "Pattern"); c(node.right, st, "Expression"); }; base.ConditionalExpression = function (node, st, c) { c(node.test, st, "Expression"); c(node.consequent, st, "Expression"); c(node.alternate, st, "Expression"); }; base.NewExpression = base.CallExpression = function (node, st, c) { c(node.callee, st, "Expression"); if (node.arguments) { for (var i = 0, list = node.arguments; i < list.length; i += 1) { var arg = list[i]; c(arg, st, "Expression"); } } }; base.MemberExpression = function (node, st, c) { c(node.object, st, "Expression"); if (node.computed) { c(node.property, st, "Expression"); } }; base.ExportNamedDeclaration = base.ExportDefaultDeclaration = function (node, st, c) { if (node.declaration) { c(node.declaration, st, node.type === "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression"); } if (node.source) { c(node.source, st, "Expression"); } if (node.attributes) { for (var i = 0, list = node.attributes; i < list.length; i += 1) { var attr = list[i]; c(attr, st); } } }; base.ExportAllDeclaration = function (node, st, c) { if (node.exported) { c(node.exported, st); } c(node.source, st, "Expression"); if (node.attributes) { for (var i = 0, list = node.attributes; i < list.length; i += 1) { var attr = list[i]; c(attr, st); } } }; base.ImportAttribute = function (node, st, c) { c(node.value, st, "Expression"); }; base.ImportDeclaration = function (node, st, c) { for (var i = 0, list = node.specifiers; i < list.length; i += 1) { var spec = list[i]; c(spec, st); } c(node.source, st, "Expression"); if (node.attributes) { for (var i$1 = 0, list$1 = node.attributes; i$1 < list$1.length; i$1 += 1) { var attr = list$1[i$1]; c(attr, st); } } }; base.ImportExpression = function (node, st, c) { c(node.source, st, "Expression"); if (node.options) { c(node.options, st, "Expression"); } }; base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.PrivateIdentifier = base.Literal = ignore; base.TaggedTemplateExpression = function (node, st, c) { c(node.tag, st, "Expression"); c(node.quasi, st, "Expression"); }; base.ClassDeclaration = base.ClassExpression = function (node, st, c) { return c(node, st, "Class"); }; base.Class = function (node, st, c) { if (node.id) { c(node.id, st, "Pattern"); } if (node.superClass) { c(node.superClass, st, "Expression"); } c(node.body, st); }; base.ClassBody = function (node, st, c) { for (var i = 0, list = node.body; i < list.length; i += 1) { var elt = list[i]; c(elt, st); } }; base.MethodDefinition = base.PropertyDefinition = base.Property = function (node, st, c) { if (node.computed) { c(node.key, st, "Expression"); } if (node.value) { c(node.value, st, "Expression"); } }; const API_NOT_FOUND_ERROR = `There are some problems in resolving the mocks API. You may encounter this issue when importing the mocks API from another module other than 'vitest'. To fix this issue you can either: - import the mocks API directly from 'vitest' - enable the 'globals' option`; function API_NOT_FOUND_CHECK(names) { return `\nif (${names.map((name) => `typeof globalThis["${name}"] === "undefined"`).join(" && ")}) ` + `{ throw new Error(${JSON.stringify(API_NOT_FOUND_ERROR)}) }\n`; } function isIdentifier(node) { return node.type === "Identifier"; } function getNodeTail(code, node) { let end = node.end; if (code[node.end] === ";") { end += 1; } if (code[node.end] === "\n") { return end + 1; } if (code[node.end + 1] === "\n") { end += 1; } return end; } const regexpHoistable = /\b(?:vi|vitest)\s*\.\s*(?:mock|unmock|hoisted|doMock|doUnmock|importActual)\s*\(/; // calls that load the original module even if it is mocked const originalLoadingMethodNames = ["doUnmock", "importActual"]; const hashbangRE = /^#!.*\n/; // Public redistributions of Vitest that re-export its mocking API (`vi`) // verbatim under their own specifier. Imports from these are treated as the // hoisted module so `vi.mock()` is hoisted for e.g. // `import { vi } from 'vite-plus/test'`, exactly as it is for `vitest`. const REDISTRIBUTED_HOISTED_MODULES = ["vite-plus/test"]; // this is a fork of Vite SSR transform function hoistMocks(code, id, parse, options = {}) { const needHoisting = (options.regexpHoistable || regexpHoistable).test(code); if (!needHoisting) { return; } const s = options.magicString?.() || new MagicString(code); let ast; try { ast = parse(code); } catch (err) { console.error(`Cannot parse ${id}:\n${err.message}.`); return; } const { hoistableMockMethodNames = ["mock", "unmock"], dynamicImportMockMethodNames = [ "mock", "unmock", "doMock", "doUnmock" ], hoistedMethodNames = ["hoisted"], utilsObjectNames = ["vi", "vitest"], hoistedModule = "vitest" } = options; // hoist at the start of the file, after the hashbang const hashbangEnd = hashbangRE.exec(code)?.[0].length ?? 0; let hoistIndex = hashbangEnd; let hoistedModuleImported = false; let uid = 0; const idToImportMap = new Map(); const imports = []; // this will transform import statements into dynamic ones, if there are imports // it will keep the import as is, if we don't need to mock anything // in browser environment it will wrap the module value with "vitest_wrap_module" function // that returns a proxy to the module so that named exports can be mocked function defineImport(importNode) { const source = importNode.source.value; // always hoist vitest import to top of the file, so // "vi" helpers can access it. Vitest redistributions that re-export the // mocking API under their own specifier are recognized the same way. if (hoistedModule === source || REDISTRIBUTED_HOISTED_MODULES.includes(source)) { hoistedModuleImported = true; return; } const importId = `__vi_import_${uid++}__`; imports.push({ id: importId, node: importNode }); return importId; } // 1. check all import statements and record id -> importName map for (const node of ast.body) { // import foo from 'foo' --> foo -> __import_foo__.default // import { baz } from 'foo' --> baz -> __import_foo__.baz // import * as ok from 'foo' --> ok -> __import_foo__ if (node.type === "ImportDeclaration") { const importId = defineImport(node); if (!importId) { continue; } for (const spec of node.specifiers) { if (spec.type === "ImportSpecifier") { if (spec.imported.type === "Identifier") { idToImportMap.set(spec.local.name, `${importId}.${spec.imported.name}`); } else { idToImportMap.set(spec.local.name, `${importId}[${JSON.stringify(spec.imported.value)}]`); } } else if (spec.type === "ImportDefaultSpecifier") { idToImportMap.set(spec.local.name, `${importId}.default`); } else { // namespace specifier idToImportMap.set(spec.local.name, importId); } } } } const declaredConst = new Set(); const hoistedNodes = new Set(); function createSyntaxError(node, message) { const _error = new SyntaxError(message); Error.captureStackTrace(_error, createSyntaxError); const serializedError = { name: "SyntaxError", message: _error.message, stack: _error.stack }; if (options.codeFrameGenerator) { serializedError.frame = options.codeFrameGenerator(node, id, code); } return serializedError; } function assertNotDefaultExport(node, error) { const defaultExport = findNodeAround(ast, node.start, "ExportDefaultDeclaration")?.node; if (defaultExport?.declaration === node || defaultExport?.declaration.type === "AwaitExpression" && defaultExport.declaration.argument === node) { throw createSyntaxError(defaultExport, error); } } function assertNotNamedExport(node, error) { const nodeExported = findNodeAround(ast, node.start, "ExportNamedDeclaration")?.node; if (nodeExported?.declaration === node) { throw createSyntaxError(nodeExported, error); } } function getVariableDeclaration(node) { const declarationNode = findNodeAround(ast, node.start, "VariableDeclaration")?.node; const init = declarationNode?.declarations[0]?.init; if (init && (init === node || init.type === "AwaitExpression" && init.argument === node)) { return declarationNode; } } function isImportMetaVitestCheck(node) { if (node.type !== "IfStatement") { return false; } const test = node.test; return code.slice(test.start, test.end) === "import.meta.vitest"; } const hoistConditions = new Map(); const inSourceBlocks = new Set(); // in-source `vi.mock` and `vi.hoisted` run only when their file is the test file function addHoistedNode(node, parentStack) { hoistedNodes.add(node); const guard = parentStack.find(isImportMetaVitestCheck); if (guard) { const test = guard.test; // copied because the native loader rewrites `import.meta.vitest` hoistConditions.set(node, s.slice(test.start, test.end)); if (guard.consequent.type === "BlockStatement") { inSourceBlocks.add(guard.consequent); } } } const usedUtilityExports = new Set(); let hasMockApiCall = false; esmWalker(ast, { // called after all call expressions are visited onIdentifier(id, info, parentStack) { const binding = idToImportMap.get(id.name); if (!binding || !hasMockApiCall) { return; } if (info.hasBindingShortcut) { s.appendLeft(id.end, `: ${binding}`); } else if (info.classDeclaration) { if (!declaredConst.has(id.name)) { declaredConst.add(id.name); // locate the top-most node containing the class declaration const topNode = parentStack[parentStack.length - 2]; s.prependRight(topNode.start, `const ${id.name} = ${binding};\n`); } } else if (!info.classExpression) { s.update(id.start, id.end, binding); } }, onDynamicImport(_node) { // TODO: vi.mock(import) breaks it, and vi.mock('', () => import) also does, // only move imports that are outside of vi.mock // backwards compat, don't do if not passed // if (!options.globalThisAccessor) { // return // } // const globalThisAccessor = options.globalThisAccessor // const replaceString = `globalThis[${globalThisAccessor}].wrapDynamicImport(() => import(` // const importSubstring = code.substring(node.start, node.end) // const hasIgnore = importSubstring.includes('/* @vite-ignore */') // s.overwrite( // node.start, // (node.source as Positioned<Expression>).start, // replaceString + (hasIgnore ? '/* @vite-ignore */ ' : ''), // ) // s.overwrite(node.end - 1, node.end, '))') }, onCallExpression(node, parentStack) { if (node.callee.type === "MemberExpression" && isIdentifier(node.callee.object) && utilsObjectNames.includes(node.callee.object.name) && isIdentifier(node.callee.property)) { const methodName = node.callee.property.name; usedUtilityExports.add(node.callee.object.name); if (hoistableMockMethodNames.includes(methodName)) { hasMockApiCall = true; const method = `${node.callee.object.name}.${methodName}`; assertNotDefaultExport(node, `Cannot export the result of "${method}". Remove export declaration because "${method}" doesn't return anything.`); const declarationNode = getVariableDeclaration(node); if (declarationNode) { assertNotNamedExport(declarationNode, `Cannot export the result of "${method}". Remove export declaration because "${method}" doesn't return anything.`); } if (options.onStaticMock) { const specifier = getStaticSpecifier(node.arguments[0]); if (specifier != null) { // anything but an inline function may still load the original const factory = node.arguments[1]?.type === "ArrowFunctionExpression" || node.arguments[1]?.type === "FunctionExpression" ? node.arguments[1] : undefined; options.onStaticMock({ method: methodName, specifier, hasFactory: factory != null, automock: node.arguments.length === 1, factoryLoadsOriginal: factory != null && (factory.params.length > 0 || code.slice(factory.start, factory.end).includes("importActual")) }); } } // rewrite vi.mock(import('..')) into vi.mock('..') if (node.type === "CallExpression" && node.callee.type === "MemberExpression" && dynamicImportMockMethodNames.includes(node.callee.property.name)) { const moduleInfo = node.arguments[0]; // vi.mock(import('./path')) -> vi.mock('./path') if (moduleInfo.type === "ImportExpression") { const source = moduleInfo.source; s.overwrite(moduleInfo.start, moduleInfo.end, s.slice(source.start, source.end)); } // vi.mock(await import('./path')) -> vi.mock('./path') if (moduleInfo.type === "AwaitExpression" && moduleInfo.argument.type === "ImportExpression") { const source = moduleInfo.argument.source; s.overwrite(moduleInfo.start, moduleInfo.end, s.slice(source.start, source.end)); } } addHoistedNode(node, parentStack); } else if (dynamicImportMockMethodNames.includes(methodName)) { hasMockApiCall = true; const moduleInfo = node.arguments[0]; let source = null; if (moduleInfo.type === "ImportExpression") { source = moduleInfo.source; } if (moduleInfo.type === "AwaitExpression" && moduleInfo.argument.type === "ImportExpression") { source = moduleInfo.argument.source; } if (source) { s.overwrite(moduleInfo.start, moduleInfo.end, s.slice(source.start, source.end)); } } if (options.onStaticMock && originalLoadingMethodNames.includes(methodName)) { const specifier = getStaticSpecifier(node.arguments[0]); if (specifier != null) { options.onStaticMock({ method: methodName, specifier, hasFactory: false, factoryLoadsOriginal: false }); } } if (hoistedMethodNames.includes(methodName)) { hasMockApiCall = true; assertNotDefaultExport(node, "Cannot export hoisted variable. You can control hoisting behavior by placing the import from this file first."); const declarationNode = getVariableDeclaration(node); if (declarationNode) { assertNotNamedExport(declarationNode, "Cannot export hoisted variable. You can control hoisting behavior by placing the import from this file first."); // hoist "const variable = vi.hoisted(() => {})" addHoistedNode(declarationNode, parentStack); } else { const awaitedExpression = findNodeAround(ast, node.start, "AwaitExpression")?.node; // hoist "await vi.hoisted(async () => {})" or "vi.hoisted(() => {})" const moveNode = awaitedExpression?.argument === node ? awaitedExpression : node; addHoistedNode(moveNode, parentStack); } } } } }); if (!hasMockApiCall) { return; } function getNodeName(node) { const callee = node.callee || {}; if (callee.type === "MemberExpression" && isIdentifier(callee.property) && isIdentifier(callee.object)) { const argument = node.arguments[0]; const argStr = argument.type === "Literal" || argument.type === "ImportExpression" ? code.slice(argument.start, argument.end) : ""; return `${callee.object.name}.${callee.property.name}(${argStr})`; } return "\"hoisted method\""; } function getNodeCall(node) { if (node.type === "CallExpression") { return node; } if (node.type === "VariableDeclaration") { const { declarations } = node; const init = declarations[0].init; if (init) { return getNodeCall(init); } } if (node.type === "AwaitExpression") { const { argument } = node; if (argument.type === "CallExpression") { return getNodeCall(argument); } } return node; } function createError(outsideNode, insideNode) { const outsideCall = getNodeCall(outsideNode); const insideCall = getNodeCall(insideNode); throw createSyntaxError(insideCall, `Cannot call ${getNodeName(insideCall)} inside ${getNodeName(outsideCall)}: both methods are hoisted to the top of the file and not actually called inside each other.`); } // validate hoistedNodes doesn't have nodes inside other nodes const arrayNodes = Array.from(hoistedNodes); for (let i = 0; i < arrayNodes.length; i++) { const node = arrayNodes[i]; for (let j = i + 1; j < arrayNodes.length; j++) { const otherNode = arrayNodes[j]; if (node.start >= otherNode.start && node.end <= otherNode.end) { throw createError(otherNode, node); } if (otherNode.start >= node.start && otherNode.end <= node.end) { throw createError(node, otherNode); } } } // validate that hoisted nodes are defined on the top level of the module // or of an `if (import.meta.vitest)` block const topLevelNodes = [...ast.body, ...Array.from(inSourceBlocks).flatMap((block) => block.body)]; for (const node of topLevelNodes) { hoistedNodes.delete(node); if (node.type === "ExpressionStatement") { hoistedNodes.delete(node.expression); } } if (hoistedNodes.size) { const locations = createIndexLocationsMap(code); const map = options.getMap && new TraceMap(options.getMap()); const plural = hoistedNodes.size > 1; const topLevel = inSourceBlocks.size ? "the top level of the `if (import.meta.vitest)` block" : "the top level"; const message = [ `${hoistedNodes.size} call${plural ? "s" : ""} in "${relative(options.root || process.cwd(), id)}" ${plural ? "were" : "was"} defined outside of the module's top level scope:`, "", ...Array.from(hoistedNodes, (invalidNode) => { const currentLocation = locations.get(invalidNode.start); const originalLocation = map && currentLocation && originalPositionFor(map, currentLocation); const location = originalLocation?.column != null && originalLocation?.line != null ? ` at ${relative(options.root || process.cwd(), id)}:${originalLocation.line}:${originalLocation.column + 1}` : ""; return `- ${getNodeName(getNodeCall(invalidNode))}${location}`; }), "", `Although ${plural ? "they appear nested, they" : "it appears nested, it"} will be hoisted and executed before anything in this file. Move ${plural ? "them" : "it"} to ${topLevel} to reflect ${plural ? "their" : "its"} actual execution order.`, "See: https://vitest.dev/guide/mocking/modules#how-it-works" ].join("\n"); throw new Error(message); } // hoist vi.mock/vi.hoisted for (const node of arrayNodes) { const end = getNodeTail(code, node); const condition = hoistConditions.get(node); if (condition) { // `var` keeps the declared names visible outside of the `if` block if (node.type === "VariableDeclaration") { s.update(node.start, node.start + node.kind.length, "var"); } s.prependRight(node.start, `if (${condition}) { `); s.appendLeft(node.end, " }"); } // don't hoist into itself if it's already at the top if (hoistIndex === end || hoistIndex === node.start) { hoistIndex = end; } else { s.move(node.start, end, hoistIndex); } } // hoist actual dynamic imports last so they are inserted after all hoisted mocks for (const { node: importNode, id: importId } of imports) { const source = importNode.source.value; const sourceString = JSON.stringify(source); let importLine = `const ${importId} = await `; if (options.globalThisAccessor) { importLine += `globalThis[${options.globalThisAccessor}].wrapDynamicImport(() => import(${sourceString}));\n`; } else { importLine += `import(${sourceString});\n`; } s.update(importNode.start, importNode.end, importLine); if (importNode.start === hoistIndex) { // no need to hoist, but update hoistIndex to keep the order hoistIndex = importNode.end; } else { // There will be an error if the module is called before it is imported, // so the module import statement is hoisted to the top s.move(importNode.start, importNode.end, hoistIndex); } } if (!hoistedModuleImported && arrayNodes.length > 0) { const utilityImports = [...usedUtilityExports]; // "vi" or "vitest" is imported from a module other than "vitest" if (utilityImports.some((name) => idToImportMap.has(name))) { s.appendLeft(hashbangEnd, API_NOT_FOUND_CHECK(utilityImports)); } else if (utilityImports.length) { s.appendLeft(hashbangEnd, `import { ${[...usedUtilityExports].join(", ")} } from ${JSON.stringify(hoistedModule)}\n`); } } return s; } function createIndexLocationsMap(source) { const map = new Map(); let offset = 0; let line = 1; let column = 1; for (const char of source) { map.set(offset++, { line, column }); if (char === "\n" || char === "\r\n") { line++; column = 0; } else { column++; } } return map; } function getStaticSpecifier(node) { if (node?.type === "AwaitExpression") { node = node.argument; } if (node?.type === "ImportExpression") { node = node.source; } if (node?.type === "Literal" && typeof node.value === "string") { return node.value; } if (node?.type === "TemplateLiteral" && node.expressions.length === 0) { return node.quasis[0].value.cooked ?? undefined; } } export { hoistMocks as h };