{"id":"6e05a49d4fc62897","repo":"vitest-dev/vitest","slug":"hoistednodes-size-call-plural-s-in","errorCode":null,"errorMessage":"${hoistedNodes.size} call${plural ? 's' : ''} in \"${relative(options.root || process.cwd(), id)}\" ${plural ? 'were' : 'was'} defined outside of the module's top level scope:\n\n${Array.from(hoistedNodes, (invalidNode) => `- ${getNodeName(getNodeCall(invalidNode))}${location}`).join('\\n')}\n\nAlthough ${plural ? 'they appear nested, they' : 'it appears nested, it'} will be hoisted and executed before anything in this file. Move ${plural ? 'them' : 'it'} to the top level to reflect ${plural ? 'their' : 'its'} actual execution order.\nSee: https://vitest.dev/guide/mocking/modules#how-it-works","messagePattern":"(.+?) call(.+?) in \"(.+?)\" (.+?) defined outside of the module's top level scope:\n\n(.+?)(.+?)`\\)\\.join\\('\\\\n'\\)\\}\n\nAlthough (.+?) will be hoisted and executed before anything in this file\\. Move (.+?) to the top level to reflect (.+?) actual execution order\\.\nSee: https://vitest\\.dev/guide/mocking/modules#how-it-works","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/mocker/src/node/hoistMocks.ts","lineNumber":526,"sourceCode":"      const locations = createIndexLocationsMap(code)\n      const map = options.getMap && new TraceMap(options.getMap() as any)\n      const plural = hoistedNodes.size > 1\n      const message = [\n        `${hoistedNodes.size} call${plural ? 's' : ''} in \"${relative(options.root || process.cwd(), id)}\" ${plural ? 'were' : 'was'} defined outside of the module's top level scope:`,\n        '',\n        ...Array.from(hoistedNodes, (invalidNode) => {\n          const currentLocation = locations.get(invalidNode.start)\n          const originalLocation = map && currentLocation && originalPositionFor(map, currentLocation)\n          const location = originalLocation?.column != null && originalLocation?.line != null\n            ? ` at ${relative(options.root || process.cwd(), id)}:${originalLocation.line}:${originalLocation.column + 1}`\n            : ''\n          return `- ${getNodeName(getNodeCall(invalidNode))}${location}`\n        }),\n        '',\n        `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 the top level to reflect ${plural ? 'their' : 'its'} actual execution order.`,\n        'See: https://vitest.dev/guide/mocking/modules#how-it-works',\n      ].join('\\n')\n      throw new Error(message)\n    }\n  }\n\n  // hoist vi.mock/vi.hoisted\n  for (const node of arrayNodes) {\n    const end = getNodeTail(code, node)\n    // don't hoist into itself if it's already at the top\n    if (hoistIndex === end || hoistIndex === node.start) {\n      hoistIndex = end\n    }\n    else {\n      s.move(node.start, end, hoistIndex)\n    }\n  }\n\n  // hoist actual dynamic imports last so they are inserted after all hoisted mocks\n  for (const { node: importNode, id: importId } of imports) {\n    const source = importNode.source.value as string","sourceCodeStart":508,"sourceCodeEnd":544,"githubUrl":"https://github.com/vitest-dev/vitest/blob/d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09/packages/mocker/src/node/hoistMocks.ts#L508-L544","documentation":"Thrown at packages/mocker/src/node/hoistMocks.ts:507-527 when, after hoisting analysis, hoistedNodes still contains calls that are not at the module's top level (and import.meta.vitest was not used). vi.mock/unmock/hoisted are hoisted to the top of the file by the transformer regardless of where they are written; nesting them in a function/block is misleading because they actually run first. Vitest errors to force the code to reflect real execution order.","triggerScenarios":"Placing vi.mock(...), vi.unmock(...), or vi.hoisted(...) inside a function body, an if/else block, a loop, a callback, or any non-top-level scope. The check at hoistMocks.ts:499-507 walks ast.body and removes top-level nodes; anything left over triggers the error.","commonSituations":"Conditionally mocking based on a flag: `if (cond) vi.mock('./x')`; wrapping mocks in a helper function for reuse; mocking inside beforeAll; attempting per-test mocks with vi.mock instead of vi.doMock.","solutions":["Move the vi.mock/vi.unmock/vi.hoisted call to the top level of the test module.","For conditionals or per-test mocks, use vi.doMock/vi.doUnmock (which are NOT hoisted and can live inside functions/tests).","If reusing across files, keep mocks at the top level of each file or use a shared setup file."],"exampleFix":"// before\nfunction setup() {\n  vi.mock('./logger')\n}\nit('logs', () => { setup(); /* ... */ })\n\n// after (top-level, hoisted)\nvi.mock('./logger')\nit('logs', () => { /* ... */ })\n\n// or use doMock for runtime-controlled mocking\nit('logs', () => {\n  vi.doMock('./logger')\n  /* ... */\n})","handlingStrategy":"validation","validationCode":"// Static check: ensure vi.mock/unmock/hoisted calls are at top level.\n// At review time, grep for these calls inside function/if/for blocks.\n//\n// Example guard helper that uses the non-hoisted API (doMock) when conditional:\nfunction mockConditionally(path: string, enabled: boolean) {\n  if (!enabled) return\n  // vi.doMock is NOT hoisted, so it is safe inside a function\n  vi.doMock(path)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep vi.mock/vi.unmock/vi.hoisted at the top level of the test module.","Use vi.doMock/vi.doUnmock for per-test or conditional mocking (these are not hoisted).","Do not wrap vi.mock calls in helper functions; place them inline at the top of the file.","Configure ESLint rules or grep checks to flag vi.mock inside nested scopes."],"tags":["hoist-mocks","top-level-scope","vi-mock","vi-domock"],"analyzedSha":"d568f8ce3739b532d5bf2c1ee1e45e8a8a473d09","analyzedAt":"2026-08-03T20:23:56.861Z","schemaVersion":2}