Skip to content

Commit 34f43ae

Browse files
authored
fix(typecheck): avoid creating a temporary tsconfig file when typechecking (#7967)
1 parent 33b930a commit 34f43ae

File tree

16 files changed

+101
-177
lines changed

16 files changed

+101
-177
lines changed

docs/advanced/api/test-project.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,12 @@ vitest.config === vitest.projects[0].globalConfig
9090

9191
This is the project's resolved test config.
9292

93+
## hash <Version>3.2.0</Version> {#hash}
94+
95+
The unique hash of this project. This value is consistent between the reruns.
96+
97+
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.
98+
9399
## vite
94100

95101
This is project's [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). All projects have their own Vite servers.

packages/vitest/LICENSE.md

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -268,35 +268,6 @@ Repository: git+https://github.com/WebReflection/flatted.git
268268
269269
---------------------------------------
270270

271-
## get-tsconfig
272-
License: MIT
273-
By: Hiroki Osame
274-
Repository: privatenumber/get-tsconfig
275-
276-
> MIT License
277-
>
278-
> Copyright (c) Hiroki Osame <[email protected]>
279-
>
280-
> Permission is hereby granted, free of charge, to any person obtaining a copy
281-
> of this software and associated documentation files (the "Software"), to deal
282-
> in the Software without restriction, including without limitation the rights
283-
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
284-
> copies of the Software, and to permit persons to whom the Software is
285-
> furnished to do so, subject to the following conditions:
286-
>
287-
> The above copyright notice and this permission notice shall be included in all
288-
> copies or substantial portions of the Software.
289-
>
290-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
291-
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
292-
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
293-
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
294-
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
295-
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
296-
> SOFTWARE.
297-
298-
---------------------------------------
299-
300271
## js-tokens
301272
License: MIT
302273
By: Simon Lydell
@@ -550,35 +521,6 @@ Repository: git+https://github.com/quansync-dev/quansync.git
550521
551522
---------------------------------------
552523

553-
## resolve-pkg-maps
554-
License: MIT
555-
By: Hiroki Osame
556-
Repository: privatenumber/resolve-pkg-maps
557-
558-
> MIT License
559-
>
560-
> Copyright (c) Hiroki Osame <[email protected]>
561-
>
562-
> Permission is hereby granted, free of charge, to any person obtaining a copy
563-
> of this software and associated documentation files (the "Software"), to deal
564-
> in the Software without restriction, including without limitation the rights
565-
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
566-
> copies of the Software, and to permit persons to whom the Software is
567-
> furnished to do so, subject to the following conditions:
568-
>
569-
> The above copyright notice and this permission notice shall be included in all
570-
> copies or substantial portions of the Software.
571-
>
572-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
573-
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
574-
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
575-
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
576-
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
577-
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
578-
> SOFTWARE.
579-
580-
---------------------------------------
581-
582524
## sisteransi
583525
License: MIT
584526
By: Terkel Gjervig

packages/vitest/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@
198198
"chai-subset": "^1.6.0",
199199
"find-up": "^6.3.0",
200200
"flatted": "catalog:",
201-
"get-tsconfig": "^4.10.0",
202201
"happy-dom": "^17.4.4",
203202
"jsdom": "^26.1.0",
204203
"local-pkg": "^1.1.1",

packages/vitest/src/node/pools/typecheck.ts

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createDefer } from '@vitest/utils'
99
import { Typechecker } from '../../typecheck/typechecker'
1010
import { groupBy } from '../../utils/base'
1111

12-
export function createTypecheckPool(ctx: Vitest): ProcessPool {
12+
export function createTypecheckPool(vitest: Vitest): ProcessPool {
1313
const promisesMap = new WeakMap<TestProject, DeferPromise<void>>()
1414
const rerunTriggered = new WeakSet<TestProject>()
1515

@@ -20,11 +20,11 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
2020
const checker = project.typechecker!
2121

2222
const { packs, events } = checker.getTestPacksAndEvents()
23-
await ctx._testRun.updated(packs, events)
23+
await vitest._testRun.updated(packs, events)
2424

2525
if (!project.config.typecheck.ignoreSourceErrors) {
2626
sourceErrors.forEach(error =>
27-
ctx.state.catchError(error, 'Unhandled Source Error'),
27+
vitest.state.catchError(error, 'Unhandled Source Error'),
2828
)
2929
}
3030

@@ -33,19 +33,19 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
3333
if (processError) {
3434
const error = new Error(checker.getOutput())
3535
error.stack = ''
36-
ctx.state.catchError(error, 'Typecheck Error')
36+
vitest.state.catchError(error, 'Typecheck Error')
3737
}
3838

3939
promisesMap.get(project)?.resolve()
4040

4141
rerunTriggered.delete(project)
4242

4343
// triggered by TSC watcher, not Vitest watcher, so we need to emulate what Vitest does in this case
44-
if (ctx.config.watch && !ctx.runningPromise) {
45-
await ctx.report('onFinished', files, [])
46-
await ctx.report('onWatcherStart', files, [
44+
if (vitest.config.watch && !vitest.runningPromise) {
45+
await vitest.report('onFinished', files, [])
46+
await vitest.report('onWatcherStart', files, [
4747
...(project.config.typecheck.ignoreSourceErrors ? [] : sourceErrors),
48-
...ctx.state.getUnhandledErrors(),
48+
...vitest.state.getUnhandledErrors(),
4949
])
5050
}
5151
}
@@ -65,19 +65,19 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
6565
checker.onParseStart(async () => {
6666
const files = checker.getTestFiles()
6767
for (const file of files) {
68-
await ctx._testRun.enqueued(project, file)
68+
await vitest._testRun.enqueued(project, file)
6969
}
70-
await ctx._testRun.collected(project, files)
70+
await vitest._testRun.collected(project, files)
7171
})
7272

7373
checker.onParseEnd(result => onParseEnd(project, result))
7474

7575
checker.onWatcherRerun(async () => {
7676
rerunTriggered.add(project)
7777

78-
if (!ctx.runningPromise) {
79-
ctx.state.clearErrors()
80-
await ctx.report(
78+
if (!vitest.runningPromise) {
79+
vitest.state.clearErrors()
80+
await vitest.report(
8181
'onWatcherRerun',
8282
files,
8383
'File change detected. Triggering rerun.',
@@ -88,15 +88,14 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
8888

8989
const testFiles = checker.getTestFiles()
9090
for (const file of testFiles) {
91-
await ctx._testRun.enqueued(project, file)
91+
await vitest._testRun.enqueued(project, file)
9292
}
93-
await ctx._testRun.collected(project, testFiles)
93+
await vitest._testRun.collected(project, testFiles)
9494

9595
const { packs, events } = checker.getTestPacksAndEvents()
96-
await ctx._testRun.updated(packs, events)
96+
await vitest._testRun.updated(packs, events)
9797
})
9898

99-
await checker.prepare()
10099
return checker
101100
}
102101

@@ -118,7 +117,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
118117
checker.setFiles(files)
119118
await checker.collectTests()
120119
const testFiles = checker.getTestFiles()
121-
ctx.state.collectFiles(project, testFiles)
120+
vitest.state.collectFiles(project, testFiles)
122121
}
123122
}
124123

@@ -147,9 +146,9 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
147146
if (project.typechecker && !triggered) {
148147
const testFiles = project.typechecker.getTestFiles()
149148
for (const file of testFiles) {
150-
await ctx._testRun.enqueued(project, file)
149+
await vitest._testRun.enqueued(project, file)
151150
}
152-
await ctx._testRun.collected(project, testFiles)
151+
await vitest._testRun.collected(project, testFiles)
153152
await onParseEnd(project, project.typechecker.getResult())
154153
continue
155154
}
@@ -166,7 +165,7 @@ export function createTypecheckPool(ctx: Vitest): ProcessPool {
166165
runTests,
167166
collectTests,
168167
async close() {
169-
const promises = ctx.projects.map(project =>
168+
const promises = vitest.projects.map(project =>
170169
project.typechecker?.stop(),
171170
)
172171
await Promise.all(promises)

packages/vitest/src/node/project.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export class TestProject {
7070
/** @internal */ typechecker?: Typechecker
7171
/** @internal */ _config?: ResolvedConfig
7272
/** @internal */ _vite?: ViteDevServer
73+
/** @internal */ _hash?: string
7374

7475
private runner!: ViteNodeRunner
7576

@@ -92,6 +93,18 @@ export class TestProject {
9293
this.globalConfig = vitest.config
9394
}
9495

96+
/**
97+
* The unique hash of this project. This value is consistent between the reruns.
98+
*
99+
* It is based on the root of the project (not consistent between OS) and its name.
100+
*/
101+
public get hash(): string {
102+
if (!this._hash) {
103+
throw new Error('The server was not set. It means that `project.hash` was called before the Vite server was established.')
104+
}
105+
return this._hash
106+
}
107+
95108
// "provide" is a property, not a method to keep the context when destructed in the global setup,
96109
// making it a method would be a breaking change, and can be done in Vitest 3 at minimum
97110
/**
@@ -601,6 +614,12 @@ export class TestProject {
601614
return this._configureServer(options, server)
602615
}
603616

617+
private _setHash() {
618+
this._hash = generateHash(
619+
this._config!.root + this._config!.name,
620+
)
621+
}
622+
604623
/** @internal */
605624
async _configureServer(options: UserConfig, server: ViteDevServer): Promise<void> {
606625
this._config = resolveConfig(
@@ -611,6 +630,7 @@ export class TestProject {
611630
},
612631
server.config,
613632
)
633+
this._setHash()
614634
for (const _providedKey in this.config.provide) {
615635
const providedKey = _providedKey as keyof ProvidedContext
616636
// type is very strict here, so we cast it to any
@@ -699,6 +719,7 @@ export class TestProject {
699719
project.runner = vitest.runner
700720
project._vite = vitest.server
701721
project._config = vitest.config
722+
project._setHash()
702723
project._provideObject(vitest.config.provide)
703724
return project
704725
}
@@ -713,6 +734,7 @@ export class TestProject {
713734
clone.runner = parent.runner
714735
clone._vite = parent._vite
715736
clone._config = config
737+
clone._setHash()
716738
clone._parent = parent
717739
clone._provideObject(config.provide)
718740
return clone
@@ -771,3 +793,16 @@ export async function initializeProject(
771793

772794
return project
773795
}
796+
797+
function generateHash(str: string): string {
798+
let hash = 0
799+
if (str.length === 0) {
800+
return `${hash}`
801+
}
802+
for (let i = 0; i < str.length; i++) {
803+
const char = str.charCodeAt(i)
804+
hash = (hash << 5) - hash + char
805+
hash = hash & hash // Convert to 32bit integer
806+
}
807+
return `${hash}`
808+
}

packages/vitest/src/typecheck/parse.ts

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,5 @@
1-
import type { TypecheckConfig } from '../node/types/config'
21
import type { RawErrsMap, TscErrorInfo } from './types'
3-
import { writeFile } from 'node:fs/promises'
4-
import os from 'node:os'
5-
import url from 'node:url'
6-
import { getTsconfig as getTsconfigContent } from 'get-tsconfig'
7-
import { basename, dirname, join, resolve } from 'pathe'
82

9-
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
103
const newLineRegExp = /\r?\n/
114
const errCodeRegExp = /error TS(?<errCode>\d+)/
125

@@ -63,50 +56,6 @@ export async function makeTscErrorInfo(
6356
]
6457
}
6558

66-
export async function getTsconfig(root: string, config: TypecheckConfig): Promise<{
67-
path: string
68-
config: Record<string, any>
69-
}> {
70-
const configName = config.tsconfig ? basename(config.tsconfig) : undefined
71-
const configSearchPath = config.tsconfig
72-
? dirname(resolve(root, config.tsconfig))
73-
: root
74-
75-
const tsconfig = getTsconfigContent(configSearchPath, configName)
76-
77-
if (!tsconfig) {
78-
throw new Error('no tsconfig.json found')
79-
}
80-
81-
const tsconfigName = basename(tsconfig.path, '.json')
82-
const tempTsConfigName = `${tsconfigName}.vitest-temp.json`
83-
const tempTsbuildinfoName = `${tsconfigName}.tmp.tsbuildinfo`
84-
85-
const tempConfigPath = join(
86-
dirname(tsconfig.path),
87-
tempTsConfigName,
88-
)
89-
90-
try {
91-
const tmpTsConfig: Record<string, any> = { ...tsconfig.config }
92-
93-
tmpTsConfig.compilerOptions = tmpTsConfig.compilerOptions || {}
94-
tmpTsConfig.compilerOptions.emitDeclarationOnly = false
95-
tmpTsConfig.compilerOptions.incremental = true
96-
tmpTsConfig.compilerOptions.tsBuildInfoFile = join(
97-
process.versions.pnp ? join(os.tmpdir(), 'vitest') : __dirname,
98-
tempTsbuildinfoName,
99-
)
100-
101-
const tsconfigFinalContent = JSON.stringify(tmpTsConfig, null, 2)
102-
await writeFile(tempConfigPath, tsconfigFinalContent)
103-
return { path: tempConfigPath, config: tmpTsConfig }
104-
}
105-
catch (err) {
106-
throw new Error(`failed to write ${tempTsConfigName}`, { cause: err })
107-
}
108-
}
109-
11059
export async function getRawErrsMapFromTsCompile(tscErrorStdout: string): Promise<RawErrsMap> {
11160
const rawErrsMap: RawErrsMap = new Map()
11261

0 commit comments

Comments
 (0)