diff --git a/docs/advanced/api/test-project.md b/docs/advanced/api/test-project.md
index d1a2f4f97..9fef2c8c1 100644
--- a/docs/advanced/api/test-project.md
+++ b/docs/advanced/api/test-project.md
@@ -90,6 +90,12 @@ vitest.config === vitest.projects[0].globalConfig
This is the project's resolved test config.
+## hash 3.2.0 {#hash}
+
+The unique hash of this project. This value is consistent between the reruns.
+
+It is based on the root of the project and its name. Note that the root path is not consistent between different OS, so the hash will also be different.
+
## vite
This is project's [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). All projects have their own Vite servers.
diff --git a/packages/vitest/LICENSE.md b/packages/vitest/LICENSE.md
index 6e39e0f09..836da1d85 100644
--- a/packages/vitest/LICENSE.md
+++ b/packages/vitest/LICENSE.md
@@ -268,35 +268,6 @@ Repository: git+https://github.com/WebReflection/flatted.git
---------------------------------------
-## get-tsconfig
-License: MIT
-By: Hiroki Osame
-Repository: privatenumber/get-tsconfig
-
-> MIT License
->
-> Copyright (c) Hiroki Osame
->
-> Permission is hereby granted, free of charge, to any person obtaining a copy
-> of this software and associated documentation files (the "Software"), to deal
-> in the Software without restriction, including without limitation the rights
-> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-> copies of the Software, and to permit persons to whom the Software is
-> furnished to do so, subject to the following conditions:
->
-> The above copyright notice and this permission notice shall be included in all
-> copies or substantial portions of the Software.
->
-> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-> SOFTWARE.
-
----------------------------------------
-
## js-tokens
License: MIT
By: Simon Lydell
@@ -550,35 +521,6 @@ Repository: git+https://github.com/quansync-dev/quansync.git
---------------------------------------
-## resolve-pkg-maps
-License: MIT
-By: Hiroki Osame
-Repository: privatenumber/resolve-pkg-maps
-
-> MIT License
->
-> Copyright (c) Hiroki Osame
->
-> Permission is hereby granted, free of charge, to any person obtaining a copy
-> of this software and associated documentation files (the "Software"), to deal
-> in the Software without restriction, including without limitation the rights
-> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-> copies of the Software, and to permit persons to whom the Software is
-> furnished to do so, subject to the following conditions:
->
-> The above copyright notice and this permission notice shall be included in all
-> copies or substantial portions of the Software.
->
-> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-> SOFTWARE.
-
----------------------------------------
-
## sisteransi
License: MIT
By: Terkel Gjervig
diff --git a/packages/vitest/package.json b/packages/vitest/package.json
index e61464c1d..41fdcf2f9 100644
--- a/packages/vitest/package.json
+++ b/packages/vitest/package.json
@@ -198,7 +198,6 @@
"chai-subset": "^1.6.0",
"find-up": "^6.3.0",
"flatted": "catalog:",
- "get-tsconfig": "^4.10.0",
"happy-dom": "^17.4.4",
"jsdom": "^26.1.0",
"local-pkg": "^1.1.1",
diff --git a/packages/vitest/src/node/pools/typecheck.ts b/packages/vitest/src/node/pools/typecheck.ts
index 8ad0ebc99..4789667f8 100644
--- a/packages/vitest/src/node/pools/typecheck.ts
+++ b/packages/vitest/src/node/pools/typecheck.ts
@@ -9,7 +9,7 @@ import { createDefer } from '@vitest/utils'
import { Typechecker } from '../../typecheck/typechecker'
import { groupBy } from '../../utils/base'
-export function createTypecheckPool(ctx: Vitest): ProcessPool {
+export function createTypecheckPool(vitest: Vitest): ProcessPool {
const promisesMap = new WeakMap>()
const rerunTriggered = new WeakSet()
@@ -20,11 +20,11 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
const checker = project.typechecker!
const { packs, events } = checker.getTestPacksAndEvents()
- await ctx._testRun.updated(packs, events)
+ await vitest._testRun.updated(packs, events)
if (!project.config.typecheck.ignoreSourceErrors) {
sourceErrors.forEach(error =>
- ctx.state.catchError(error, 'Unhandled Source Error'),
+ vitest.state.catchError(error, 'Unhandled Source Error'),
)
}
@@ -33,7 +33,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
if (processError) {
const error = new Error(checker.getOutput())
error.stack = ''
- ctx.state.catchError(error, 'Typecheck Error')
+ vitest.state.catchError(error, 'Typecheck Error')
}
promisesMap.get(project)?.resolve()
@@ -41,11 +41,11 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
rerunTriggered.delete(project)
// triggered by TSC watcher, not Vitest watcher, so we need to emulate what Vitest does in this case
- if (ctx.config.watch && !ctx.runningPromise) {
- await ctx.report('onFinished', files, [])
- await ctx.report('onWatcherStart', files, [
+ if (vitest.config.watch && !vitest.runningPromise) {
+ await vitest.report('onFinished', files, [])
+ await vitest.report('onWatcherStart', files, [
...(project.config.typecheck.ignoreSourceErrors ? [] : sourceErrors),
- ...ctx.state.getUnhandledErrors(),
+ ...vitest.state.getUnhandledErrors(),
])
}
}
@@ -65,9 +65,9 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
checker.onParseStart(async () => {
const files = checker.getTestFiles()
for (const file of files) {
- await ctx._testRun.enqueued(project, file)
+ await vitest._testRun.enqueued(project, file)
}
- await ctx._testRun.collected(project, files)
+ await vitest._testRun.collected(project, files)
})
checker.onParseEnd(result => onParseEnd(project, result))
@@ -75,9 +75,9 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
checker.onWatcherRerun(async () => {
rerunTriggered.add(project)
- if (!ctx.runningPromise) {
- ctx.state.clearErrors()
- await ctx.report(
+ if (!vitest.runningPromise) {
+ vitest.state.clearErrors()
+ await vitest.report(
'onWatcherRerun',
files,
'File change detected. Triggering rerun.',
@@ -88,15 +88,14 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
const testFiles = checker.getTestFiles()
for (const file of testFiles) {
- await ctx._testRun.enqueued(project, file)
+ await vitest._testRun.enqueued(project, file)
}
- await ctx._testRun.collected(project, testFiles)
+ await vitest._testRun.collected(project, testFiles)
const { packs, events } = checker.getTestPacksAndEvents()
- await ctx._testRun.updated(packs, events)
+ await vitest._testRun.updated(packs, events)
})
- await checker.prepare()
return checker
}
@@ -118,7 +117,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
checker.setFiles(files)
await checker.collectTests()
const testFiles = checker.getTestFiles()
- ctx.state.collectFiles(project, testFiles)
+ vitest.state.collectFiles(project, testFiles)
}
}
@@ -147,9 +146,9 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
if (project.typechecker && !triggered) {
const testFiles = project.typechecker.getTestFiles()
for (const file of testFiles) {
- await ctx._testRun.enqueued(project, file)
+ await vitest._testRun.enqueued(project, file)
}
- await ctx._testRun.collected(project, testFiles)
+ await vitest._testRun.collected(project, testFiles)
await onParseEnd(project, project.typechecker.getResult())
continue
}
@@ -166,7 +165,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
runTests,
collectTests,
async close() {
- const promises = ctx.projects.map(project =>
+ const promises = vitest.projects.map(project =>
project.typechecker?.stop(),
)
await Promise.all(promises)
diff --git a/packages/vitest/src/node/project.ts b/packages/vitest/src/node/project.ts
index 5edf19c58..4d193a8e0 100644
--- a/packages/vitest/src/node/project.ts
+++ b/packages/vitest/src/node/project.ts
@@ -70,6 +70,7 @@ export class TestProject {
/** @internal */ typechecker?: Typechecker
/** @internal */ _config?: ResolvedConfig
/** @internal */ _vite?: ViteDevServer
+ /** @internal */ _hash?: string
private runner!: ViteNodeRunner
@@ -92,6 +93,18 @@ export class TestProject {
this.globalConfig = vitest.config
}
+ /**
+ * The unique hash of this project. This value is consistent between the reruns.
+ *
+ * It is based on the root of the project (not consistent between OS) and its name.
+ */
+ public get hash(): string {
+ if (!this._hash) {
+ throw new Error('The server was not set. It means that `project.hash` was called before the Vite server was established.')
+ }
+ return this._hash
+ }
+
// "provide" is a property, not a method to keep the context when destructed in the global setup,
// making it a method would be a breaking change, and can be done in Vitest 3 at minimum
/**
@@ -601,6 +614,12 @@ export class TestProject {
return this._configureServer(options, server)
}
+ private _setHash() {
+ this._hash = generateHash(
+ this._config!.root + this._config!.name,
+ )
+ }
+
/** @internal */
async _configureServer(options: UserConfig, server: ViteDevServer): Promise {
this._config = resolveConfig(
@@ -611,6 +630,7 @@ export class TestProject {
},
server.config,
)
+ this._setHash()
for (const _providedKey in this.config.provide) {
const providedKey = _providedKey as keyof ProvidedContext
// type is very strict here, so we cast it to any
@@ -699,6 +719,7 @@ export class TestProject {
project.runner = vitest.runner
project._vite = vitest.server
project._config = vitest.config
+ project._setHash()
project._provideObject(vitest.config.provide)
return project
}
@@ -713,6 +734,7 @@ export class TestProject {
clone.runner = parent.runner
clone._vite = parent._vite
clone._config = config
+ clone._setHash()
clone._parent = parent
clone._provideObject(config.provide)
return clone
@@ -771,3 +793,16 @@ export async function initializeProject(
return project
}
+
+function generateHash(str: string): string {
+ let hash = 0
+ if (str.length === 0) {
+ return `${hash}`
+ }
+ for (let i = 0; i < str.length; i++) {
+ const char = str.charCodeAt(i)
+ hash = (hash << 5) - hash + char
+ hash = hash & hash // Convert to 32bit integer
+ }
+ return `${hash}`
+}
diff --git a/packages/vitest/src/typecheck/parse.ts b/packages/vitest/src/typecheck/parse.ts
index 26e72c54a..a61bcb590 100644
--- a/packages/vitest/src/typecheck/parse.ts
+++ b/packages/vitest/src/typecheck/parse.ts
@@ -1,12 +1,5 @@
-import type { TypecheckConfig } from '../node/types/config'
import type { RawErrsMap, TscErrorInfo } from './types'
-import { writeFile } from 'node:fs/promises'
-import os from 'node:os'
-import url from 'node:url'
-import { getTsconfig as getTsconfigContent } from 'get-tsconfig'
-import { basename, dirname, join, resolve } from 'pathe'
-const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
const newLineRegExp = /\r?\n/
const errCodeRegExp = /error TS(?\d+)/
@@ -63,50 +56,6 @@ export async function makeTscErrorInfo(
]
}
-export async function getTsconfig(root: string, config: TypecheckConfig): Promise<{
- path: string
- config: Record
-}> {
- const configName = config.tsconfig ? basename(config.tsconfig) : undefined
- const configSearchPath = config.tsconfig
- ? dirname(resolve(root, config.tsconfig))
- : root
-
- const tsconfig = getTsconfigContent(configSearchPath, configName)
-
- if (!tsconfig) {
- throw new Error('no tsconfig.json found')
- }
-
- const tsconfigName = basename(tsconfig.path, '.json')
- const tempTsConfigName = `${tsconfigName}.vitest-temp.json`
- const tempTsbuildinfoName = `${tsconfigName}.tmp.tsbuildinfo`
-
- const tempConfigPath = join(
- dirname(tsconfig.path),
- tempTsConfigName,
- )
-
- try {
- const tmpTsConfig: Record = { ...tsconfig.config }
-
- tmpTsConfig.compilerOptions = tmpTsConfig.compilerOptions || {}
- tmpTsConfig.compilerOptions.emitDeclarationOnly = false
- tmpTsConfig.compilerOptions.incremental = true
- tmpTsConfig.compilerOptions.tsBuildInfoFile = join(
- process.versions.pnp ? join(os.tmpdir(), 'vitest') : __dirname,
- tempTsbuildinfoName,
- )
-
- const tsconfigFinalContent = JSON.stringify(tmpTsConfig, null, 2)
- await writeFile(tempConfigPath, tsconfigFinalContent)
- return { path: tempConfigPath, config: tmpTsConfig }
- }
- catch (err) {
- throw new Error(`failed to write ${tempTsConfigName}`, { cause: err })
- }
-}
-
export async function getRawErrsMapFromTsCompile(tscErrorStdout: string): Promise {
const rawErrsMap: RawErrsMap = new Map()
diff --git a/packages/vitest/src/typecheck/typechecker.ts b/packages/vitest/src/typecheck/typechecker.ts
index e0470a8cb..41eb337b8 100644
--- a/packages/vitest/src/typecheck/typechecker.ts
+++ b/packages/vitest/src/typecheck/typechecker.ts
@@ -8,14 +8,15 @@ import type { TestProject } from '../node/project'
import type { Awaitable } from '../types/general'
import type { FileInformation } from './collect'
import type { TscErrorInfo } from './types'
-import { rm } from 'node:fs/promises'
+import os from 'node:os'
import { performance } from 'node:perf_hooks'
import { eachMapping, generatedPositionFor, TraceMap } from '@vitest/utils/source-map'
-import { basename, extname, resolve } from 'pathe'
+import { basename, join, resolve } from 'pathe'
import { x } from 'tinyexec'
+import { distDir } from '../paths'
import { convertTasksToEvents } from '../utils/tasks'
import { collectTests } from './collect'
-import { getRawErrsMapFromTsCompile, getTsconfig } from './parse'
+import { getRawErrsMapFromTsCompile } from './parse'
import { createIndexMap } from './utils'
export class TypeCheckError extends Error {
@@ -49,13 +50,11 @@ export class Typechecker {
private _startTime = 0
private _output = ''
private _tests: Record | null = {}
- private tempConfigPath?: string
- private allowJs?: boolean
private process?: ChildProcess
protected files: string[] = []
- constructor(protected ctx: TestProject) {}
+ constructor(protected project: TestProject) {}
public setFiles(files: string[]): void {
this.files = files
@@ -76,14 +75,11 @@ export class Typechecker {
protected async collectFileTests(
filepath: string,
): Promise {
- return collectTests(this.ctx, filepath)
+ return collectTests(this.project, filepath)
}
protected getFiles(): string[] {
- return this.files.filter((filename) => {
- const extension = extname(filename)
- return extension !== '.js' || this.allowJs
- })
+ return this.files
}
public async collectTests(): Promise> {
@@ -225,7 +221,7 @@ export class Typechecker {
{ error: TypeCheckError; originalError: TscErrorInfo }[]
>()
errorsMap.forEach((errors, path) => {
- const filepath = resolve(this.ctx.config.root, path)
+ const filepath = resolve(this.project.config.root, path)
const suiteErrors = errors.map((info) => {
const limit = Error.stackTraceLimit
Error.stackTraceLimit = 0
@@ -260,14 +256,7 @@ export class Typechecker {
return typesErrors
}
- public async clear(): Promise {
- if (this.tempConfigPath) {
- await rm(this.tempConfigPath, { force: true })
- }
- }
-
public async stop(): Promise {
- await this.clear()
this.process?.kill()
this.process = undefined
}
@@ -280,15 +269,6 @@ export class Typechecker {
await ctx.packageInstaller.ensureInstalled(packageName, ctx.config.root)
}
- public async prepare(): Promise {
- const { root, typecheck } = this.ctx.config
-
- const { config, path } = await getTsconfig(root, typecheck)
-
- this.tempConfigPath = path
- this.allowJs = typecheck.allowJs || config.allowJs || false
- }
-
public getExitCode(): number | false {
return this.process?.exitCode != null && this.process.exitCode
}
@@ -302,20 +282,29 @@ export class Typechecker {
return
}
- if (!this.tempConfigPath) {
- throw new Error('tsconfig was not initialized')
- }
-
- const { root, watch, typecheck } = this.ctx.config
-
- const args = ['--noEmit', '--pretty', 'false', '-p', this.tempConfigPath]
- // use builtin watcher, because it's faster
+ const { root, watch, typecheck } = this.project.config
+
+ const args = [
+ '--noEmit',
+ '--pretty',
+ 'false',
+ '--incremental',
+ '--tsBuildInfoFile',
+ join(
+ process.versions.pnp ? join(os.tmpdir(), this.project.hash) : distDir,
+ 'tsconfig.tmp.tsbuildinfo',
+ ),
+ ]
+ // use builtin watcher because it's faster
if (watch) {
args.push('--watch')
}
if (typecheck.allowJs) {
args.push('--allowJs', '--checkJs')
}
+ if (typecheck.tsconfig) {
+ args.push('-p', resolve(root, typecheck.tsconfig))
+ }
this._output = ''
this._startTime = performance.now()
const child = x(typecheck.checker, args, {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2b9666c02..6bb231822 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1073,9 +1073,6 @@ importers:
flatted:
specifier: 'catalog:'
version: 3.3.3
- get-tsconfig:
- specifier: ^4.10.0
- version: 4.10.0
happy-dom:
specifier: ^17.4.4
version: 17.4.6
diff --git a/test/reporters/tsconfig.json b/test/reporters/tsconfig.json
index 57e707262..791c9498a 100644
--- a/test/reporters/tsconfig.json
+++ b/test/reporters/tsconfig.json
@@ -1,4 +1,4 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.build.json",
"include": ["./tests/**/*.test-d.ts"]
}
diff --git a/test/typescript/package.json b/test/typescript/package.json
index 6f826f281..987881038 100644
--- a/test/typescript/package.json
+++ b/test/typescript/package.json
@@ -3,7 +3,7 @@
"type": "module",
"scripts": {
"test": "vitest --test-timeout 60000",
- "types": "vitest --typecheck --run",
+ "types": "vitest --typecheck.only --typecheck.allowJs --run",
"tsc": "tsc --watch --pretty false --noEmit"
},
"dependencies": {
diff --git a/test/typescript/test/__snapshots__/runner.test.ts.snap b/test/typescript/test/__snapshots__/runner.test.ts.snap
index 547dd7ad2..266ad1763 100644
--- a/test/typescript/test/__snapshots__/runner.test.ts.snap
+++ b/test/typescript/test/__snapshots__/runner.test.ts.snap
@@ -96,7 +96,7 @@ Vitest caught 1 unhandled error during the test run.
This might cause false positive tests. Resolve unhandled errors to make sure your tests are not affected.
⎯⎯⎯⎯⎯⎯ Typecheck Error ⎯⎯⎯⎯⎯⎯⎯
-Error: error TS18003: No inputs were found in config file '/tsconfig.empty.vitest-temp.json'. Specified 'include' paths were '["src"]' and 'exclude' paths were '["**/dist/**"]'.
+Error: error TS18003: No inputs were found in config file '/tsconfig.empty.json'. Specified 'include' paths were '["src"]' and 'exclude' paths were '["**/dist/**"]'.
⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯
diff --git a/test/typescript/tsconfig.custom.json b/test/typescript/tsconfig.custom.json
index 6f480114f..74f6daba3 100644
--- a/test/typescript/tsconfig.custom.json
+++ b/test/typescript/tsconfig.custom.json
@@ -1,5 +1,5 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.build.json",
"include": [
"**/fail.test-d.ts"
],
diff --git a/test/typescript/tsconfig.empty.json b/test/typescript/tsconfig.empty.json
index 6189af55b..fe6a2c24a 100644
--- a/test/typescript/tsconfig.empty.json
+++ b/test/typescript/tsconfig.empty.json
@@ -1,5 +1,5 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.build.json",
"include": [
"src"
],
diff --git a/test/typescript/tsconfig.fails.json b/test/typescript/tsconfig.fails.json
index 976ac5b3b..f912e9d7a 100644
--- a/test/typescript/tsconfig.fails.json
+++ b/test/typescript/tsconfig.fails.json
@@ -1,5 +1,5 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.build.json",
"include": [
"./failing/*"
],
diff --git a/test/typescript/tsconfig.json b/test/typescript/tsconfig.json
index f7781f12f..7663d8043 100644
--- a/test/typescript/tsconfig.json
+++ b/test/typescript/tsconfig.json
@@ -1,5 +1,5 @@
{
- "extends": "../../tsconfig.base.json",
+ "extends": "../../tsconfig.build.json",
"include": [
"./**/*.ts",
"./**/*.js"
diff --git a/tsconfig.build.json b/tsconfig.build.json
new file mode 100644
index 000000000..4e6d50061
--- /dev/null
+++ b/tsconfig.build.json
@@ -0,0 +1,8 @@
+{
+ "extends": "./tsconfig.base.json",
+ "compilerOptions": {
+ "paths": {
+ "vitest": ["./packages/vitest/dist/index.d.ts"]
+ }
+ }
+}