Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/vitest-dev/vscode. VS Code extension for Vitest vitest.dev/vscode
testing vitest vscode
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355import { existsSync, readFileSync } from 'node:fs'import * as vscode from 'vscode'import { basename, dirname, normalize, resolve } from 'pathe'import { gte } from 'semver'import { log } from '../log'import { configGlob, minimumVersion, workspaceGlob } from '../constants'import { getConfig } from '../config'import { resolveVitestPackage } from './resolve'
const _require = require
function nonNullable<T>(value: T | null | undefined): value is T { return value != null}
export interface VitestPackage { folder: vscode.WorkspaceFolder prefix: string vitestNodePath: string vitestPackageJsonPath: string // path to a config file or a workspace config file id: string cwd: string version: string arguments?: string configFile?: string workspaceFile?: string loader?: string pnp?: string}
function isVitestInPackageJson(root: string) { const pkgJson = resolve(dirname(root), 'package.json') if (existsSync(pkgJson)) { const pkg = JSON.parse(readFileSync(pkgJson, 'utf-8')) return pkg.dependencies?.vitest || pkg.devDependencies?.vitest } return false}
function resolveVitestConfig(showWarning: boolean, configOrWorkspaceFile: vscode.Uri): VitestPackage | null { const folder = vscode.workspace.getWorkspaceFolder(configOrWorkspaceFile)! if (!folder) throw new Error(`Workspace folder not found for ${configOrWorkspaceFile}. Does the file exist?`)
const cwd = dirname(configOrWorkspaceFile.fsPath) const vitest = resolveVitestPackage(dirname(configOrWorkspaceFile.fsPath), folder)
if (!vitest) { if (showWarning) { const isVitestConfig = configOrWorkspaceFile.fsPath.includes('vitest.') if (isVitestConfig || isVitestInPackageJson(folder.uri.fsPath)) vscode.window.showWarningMessage(`Vitest not found in "${basename(dirname(configOrWorkspaceFile.fsPath))}" folder. Please run \`npm i --save-dev vitest\` to install Vitest.'`) } log.error('[API]', `Vitest not found for ${configOrWorkspaceFile}.`) return null }
const id = normalize(configOrWorkspaceFile.fsPath) const prefix = `${basename(dirname(id))}:${basename(id)}`
if (vitest.pnp) { return { folder, id, cwd, prefix, vitestNodePath: vitest.vitestNodePath, vitestPackageJsonPath: vitest.vitestPackageJsonPath, version: 'pnp', loader: vitest.pnp.loaderPath, pnp: vitest.pnp.pnpPath, } }
const pkg = _require(vitest.vitestPackageJsonPath) if (!validateVitestPkg(showWarning, vitest.vitestPackageJsonPath, pkg)) return null
return { folder, id, cwd, prefix, vitestPackageJsonPath: vitest.vitestPackageJsonPath, vitestNodePath: vitest.vitestNodePath, version: pkg.version, }}
function validateVitestPkg(showWarning: boolean, pkgJsonPath: string, pkg: any) { if (pkg.name !== 'vitest') { vscode.window.showErrorMessage( `Package was resolved to "${pkg.name}" instead of "vitest". If you are using "vitest.vitestPackagePath", make sure it points to a "vitest" package.`, ) delete require.cache[pkgJsonPath] return false } if (!gte(pkg.version, minimumVersion)) { const warning = `Vitest v${pkg.version} is not supported. Vitest v${minimumVersion} or newer is required.` if (showWarning) vscode.window.showWarningMessage(warning) else log.error('[API]', `Vitest v${pkg.version} from ${pkgJsonPath} is not supported. Vitest v${minimumVersion} or newer is required.`) delete require.cache[pkgJsonPath] return false } return true}
export async function resolveVitestPackages(showWarning: boolean): Promise<VitestPackage[]> { // try to resolve configs first const vitest = await resolveVitestPackagesViaConfigs(showWarning) // if no viable configs found, try to resolve via package.json if (!vitest.meta.length && !vitest.warned) { const pkg = await resolveVitestPackagesViaPackageJson(showWarning) if (!pkg.meta.length && !pkg.warned) return resolveVitestWorkspacePackages(showWarning).meta return pkg.meta } return vitest.meta}
function resolveVitestWorkspacePackages(showWarning: boolean) { let warned = false const meta: VitestPackage[] = [] vscode.workspace.workspaceFolders?.forEach((folder) => { const cwd = normalize(folder.uri.fsPath) const vitest = resolveVitestPackage(cwd, folder) if (!vitest) return
const pkg = _require(vitest.vitestPackageJsonPath) if (!validateVitestPkg(showWarning, vitest.vitestPackageJsonPath, pkg)) { warned = true return } const id = normalize(folder.uri.fsPath) const prefix = `${basename(cwd)}:${basename(id)}` meta.push({ folder, id, cwd, prefix, vitestPackageJsonPath: vitest.vitestPackageJsonPath, vitestNodePath: vitest.vitestNodePath, version: pkg.version, }) }) return { meta, warned, }}
export async function resolveVitestPackagesViaPackageJson(showWarning: boolean): Promise<{ meta: VitestPackage[]; warned: boolean }> { const config = getConfig()
const packages = await vscode.workspace.findFiles( '**/package.json', config.configSearchPatternExclude, )
let warned = false const meta: VitestPackage[] = [] packages.forEach((pkgPath) => { const scripts = Object.entries(_require(pkgPath.fsPath).scripts || {}).filter(([, script]) => { return typeof script === 'string' && script.startsWith('vitest ') }) as [string, string][]
if (!scripts.length) return
const folder = vscode.workspace.getWorkspaceFolder(pkgPath)! const cwd = dirname(pkgPath.fsPath) const vitest = resolveVitestPackage(cwd, folder)
// skip if Vitest is not installed if (!vitest) return
const pkg = _require(vitest.vitestPackageJsonPath) if (!validateVitestPkg(showWarning, vitest.vitestPackageJsonPath, pkg)) { warned = true return }
// take only the fist script to not pollute the list const scriptOption = scripts[0]
if (!scriptOption) return
const [scriptName, script] = scriptOption
const id = `${normalize(pkgPath.fsPath)}/${scriptName}` const prefix = `${basename(cwd)}/package.json:${scriptName}` meta.push({ folder, id, cwd, prefix, arguments: script, vitestPackageJsonPath: vitest.vitestPackageJsonPath, vitestNodePath: vitest.vitestNodePath, version: pkg.version, }) })
return { meta: resolvePackagUniquePrefixes(meta), warned, }}
export async function resolveVitestPackagesViaConfigs(showWarning: boolean): Promise<{ meta: VitestPackage[]; warned: boolean }> { const config = getConfig() const userWorkspace = config.workspaceConfig const rootConfig = config.rootConfig
if (userWorkspace) log.info('[API] Using user workspace config:', userWorkspace)
const vitestWorkspaces = userWorkspace ? [vscode.Uri.file(userWorkspace)] : await vscode.workspace.findFiles(workspaceGlob, config.configSearchPatternExclude)
let warned = false
if (vitestWorkspaces.length) { // if there is a workspace config, use it as root const meta = resolvePackagUniquePrefixes(vitestWorkspaces.map((config) => { const vitest = resolveVitestConfig(showWarning, config) if (!vitest) { warned = true return null } return { ...vitest, configFile: rootConfig, workspaceFile: vitest.id, } }).filter(nonNullable))
return { meta, warned, } }
if (rootConfig) log.info('[API] Using user root config:', rootConfig)
const configs = rootConfig ? [vscode.Uri.file(rootConfig)] : await vscode.workspace.findFiles(configGlob, config.configSearchPatternExclude)
const configsByFolder = configs.reduce<Record<string, vscode.Uri[]>>((acc, config) => { const dir = dirname(config.fsPath) if (!acc[dir]) acc[dir] = [] acc[dir].push(config) return acc }, {})
const resolvedMeta: VitestPackage[] = []
for (const [_, configFiles] of Object.entries(configsByFolder)) { // vitest config always overrides vite config - if there is a Vitest config, we assume vite was overriden, // but it's possible to have several Vitest configs (vitest.e2e. vitest.unit, etc.) const hasViteAndVitestConfig = configFiles.some(file => basename(file.fsPath).includes('vite.')) && configFiles.some(file => basename(file.fsPath).includes('vitest.')) // remove all vite configs from a folder if there is at least one Vitest config const filteredConfigFiles = hasViteAndVitestConfig ? configFiles.filter(file => !basename(file.fsPath).includes('vite.')) : configFiles filteredConfigFiles.forEach((config) => { const vitest = resolveVitestConfig(showWarning, config) if (vitest) { resolvedMeta.push({ ...vitest, configFile: vitest.id, }) } else { warned = true } }) }
return { meta: resolvePackagUniquePrefixes(resolvedMeta), warned, }}
export function findFirstUniqueFolderNames(paths: string[]) { const folders: string[] = [] const mapCount: Record<string, number> = {} const segments = paths.map(p => p.split('/').reverse().slice(2))
paths.forEach((_, index) => { segments[index].forEach((str) => { if (!str) return mapCount[str] = (mapCount[str] || 0) + 1 }) })
paths.forEach((_, index) => { let minCount = Number.POSITIVE_INFINITY let folder = ''
segments[index].forEach((str) => { // in case the count is the same and we already used it, ignore it if (mapCount[str] < minCount && !folders.includes(str)) { minCount = mapCount[str] folder = str } })
mapCount[folder]++ folders.push(folder) })
return folders}
function resolvePackagUniquePrefixes(packages: VitestPackage[]) { const prefixes: Record<string, string[]> = {} const projects: Record<string, VitestPackage> = {} for (const pkg of packages) { const { prefix, id } = pkg if (!prefixes[prefix]) prefixes[prefix] = [] prefixes[prefix].push(id) projects[id] = pkg }
for (const prefix in prefixes) { const paths = prefixes[prefix] if (paths.length === 1) continue
const folders = findFirstUniqueFolderNames(paths) paths.forEach((path, index) => { const config = basename(path) const folder = folders[index] projects[path].prefix = `${folder}:${config}` }) }
return packages}