| 15 | |
| 16 | // TODO: better source map replacement |
| 17 | export function automockModule( |
| 18 | code: string, |
| 19 | mockType: 'automock' | 'autospy', |
| 20 | parse: (code: string) => any, |
| 21 | options: AutomockOptions = {}, |
| 22 | ): MagicString { |
| 23 | const globalThisAccessor = options.globalThisAccessor || '"__vitest_mocker__"' |
| 24 | let ast: Program |
| 25 | try { |
| 26 | ast = parse(code) as Program |
| 27 | } |
| 28 | catch (cause) { |
| 29 | if (options.id) { |
| 30 | throw new Error(`failed to parse ${options.id}`, { cause }) |
| 31 | } |
| 32 | throw cause |
| 33 | } |
| 34 | |
| 35 | const m = new MagicString(code) |
| 36 | |
| 37 | const allSpecifiers: { name: string; alias?: string }[] = [] |
| 38 | const replacers: (() => void)[] = [] |
| 39 | let importIndex = 0 |
| 40 | for (const _node of ast.body) { |
| 41 | if (_node.type === 'ExportAllDeclaration') { |
| 42 | const node = _node as Positioned<ExportAllDeclaration> |
| 43 | // TODO: pass it down in the browser mode |
| 44 | if (!options.id) { |
| 45 | throw new Error( |
| 46 | `automocking files with \`export *\` is not supported because it cannot be easily statically analysed`, |
| 47 | ) |
| 48 | } |
| 49 | |
| 50 | const source = node.source.value |
| 51 | if (typeof source !== 'string') { |
| 52 | throw new TypeError(`unknown source type while automocking: ${source}`) |
| 53 | } |
| 54 | |
| 55 | const moduleUrl = import.meta.resolve(source, pathToFileURL(options.id).toString()) |
| 56 | const modulePath = fileURLToPath(moduleUrl) |
| 57 | const moduleContent = readFileSync(modulePath, 'utf-8') |
| 58 | const transformedCode = transformCode(moduleContent, moduleUrl) |
| 59 | const moduleFormat = resolveModuleFormat(moduleUrl, transformedCode) |
| 60 | const moduleExports = collectModuleExports(modulePath, transformedCode, moduleFormat || 'module') |
| 61 | replacers.push(() => { |
| 62 | const importNames: string[] = [] |
| 63 | moduleExports.forEach((exportName) => { |
| 64 | const isReexported = allSpecifiers.some(({ name, alias }) => name === exportName || alias === exportName) |
| 65 | if (!isReexported) { |
| 66 | importNames.push(exportName) |
| 67 | allSpecifiers.push({ name: exportName }) |
| 68 | } |
| 69 | }) |
| 70 | |
| 71 | const importString = `import { ${importNames.join(', ')} } from '${source}';` |
| 72 | |
| 73 | m.overwrite(node.start, node.end, importString) |
| 74 | }) |