Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: introduce vitest.experimentalStaticAstCollect option #501

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,7 @@ These options are resolved relative to the [workspace file](https://code.visuals
- `vitest.debuggerPort`: Port that the debugger will be attached to. By default uses 9229 or tries to find a free port if it's not available.
- `vitest.debuggerAddress`: TCP/IP address of process to be debugged. Default: localhost

> [!NOTE]
> The `vitest.nodeExecutable` and `vitest.nodeExecArgs` settings are used as `execPath` and `execArgv` when spawning a new `child_process`, and as `runtimeExecutable` and `runtimeArgs` when [debugging a test](https://github.com/microsoft/vscode-js-debug/blob/main/OPTIONS.md).
> 💡 The `vitest.nodeExecutable` and `vitest.nodeExecArgs` settings are used as `execPath` and `execArgv` when spawning a new `child_process`, and as `runtimeExecutable` and `runtimeArgs` when [debugging a test](https://github.com/microsoft/vscode-js-debug/blob/main/OPTIONS.md).
> The `vitest.terminalShellPath` and `vitest.terminalShellArgs` settings are used as `shellPath` and `shellArgs` when creating a new [terminal](https://code.visualstudio.com/api/references/vscode-api#Terminal)

### Other Options
Expand All @@ -96,6 +95,10 @@ These options are resolved relative to the [workspace file](https://code.visuals
- `vitest.maximumConfigs`: The maximum amount of configs that Vitest extension can load. If exceeded, the extension will show a warning suggesting to use a workspace config file. Default: `3`
- `vitest.logLevel`: How verbose should the logger be in the "Output" channel. Default: `info`

### Experimental

If the extension hangs, consider enabling `vitest.experimentalStaticAstCollect` option to use static analysis instead of actually running the test file every time you make a change which can cause visible hangs if it takes a long time to setup the test.

## FAQs (Frequently Asked Questions)

### How can I use it in monorepo?
Expand Down
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@
"markdownDescription": "The arguments to pass to the shell executable. This is applied only when `vitest.shellType` is `terminal`.",
"type": "array",
"scope": "resource"
},
"vitest.experimentalStaticAstCollect": {
"markdownDescription": "Enable static AST analysis for faster test discovery. This feature is experimental and may not work with all projects.",
"type": "boolean",
"default": false,
"scope": "resource"
}
}
}
Expand Down
1 change: 1 addition & 0 deletions samples/basic/.vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"vitest.nodeEnv": {
"TEST_CUSTOM_ENV": "hello"
},
"vitest.experimentalStaticAstCollect": true,
"[typescript]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
}
Expand Down
2 changes: 1 addition & 1 deletion samples/basic/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { defineConfig } from 'vite'

export default defineConfig({
esbuild: {
target: 'es2020',
target: 'es2022',
},
test: {
include: ['src/should_included_test.ts', 'test/**/*.test.ts'],
Expand Down
1 change: 1 addition & 0 deletions src/api/child_process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ async function createChildVitestProcess(pkg: VitestPackage) {
: undefined,
},
debug: false,
astCollect: getConfig(pkg.folder).experimentalStaticAstCollect,
}

vitest.send(runnerOptions)
Expand Down
1 change: 1 addition & 0 deletions src/api/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export function waitForWsResolvedMeta(
: undefined,
},
debug,
astCollect: getConfig(pkg.folder).experimentalStaticAstCollect,
}

ws.send(JSON.stringify(runnerOptions))
Expand Down
3 changes: 3 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
const shellType = get<'child_process' | 'terminal'>('shellType', 'child_process')
const nodeExecArgs = get<string[] | undefined>('nodeExecArgs')

const experimentalStaticAstCollect = get<boolean>('experimentalStaticAstCollect', false)!

return {
env: get<null | Record<string, string>>('nodeEnv', null),
debugExclude: get<string[]>('debugExclude', []),
Expand All @@ -67,6 +69,7 @@ export function getConfig(workspaceFolder?: WorkspaceFolder) {
terminalShellPath,
shellType,
nodeExecArgs,
experimentalStaticAstCollect,
vitestPackagePath: resolvedVitestPackagePath,
workspaceConfig: resolveConfigPath(workspaceConfig),
rootConfig: resolveConfigPath(rootConfigFile),
Expand Down
1 change: 1 addition & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ class VitestExtension {
'vitest.terminalShellArgs',
'vitest.terminalShellPath',
'vitest.filesWatcherInclude',
'vitest.experimentalStaticAstCollect',
]

this.disposables = [
Expand Down
1 change: 1 addition & 0 deletions src/testTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,7 @@ export class TestTree extends vscode.Disposable {
if (file.result?.errors) {
const error = file.result.errors.map(error => error.stack || error.message).join('\n')
fileTestItem.error = error
log.error(`Error in ${file.filepath}`, error)
}
else if (!file.tasks.length) {
fileTestItem.error = `No tests found in ${file.filepath}`
Expand Down
3 changes: 2 additions & 1 deletion src/worker/collect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,9 @@ export function astParseFile(filepath: string, code: string) {
export async function astCollectTests(
ctx: WorkspaceProject,
filepath: string,
transformMode: 'web' | 'ssr',
): Promise<null | FileInformation> {
const request = await ctx.vitenode.transformRequest(filepath, filepath, 'web')
const request = await ctx.vitenode.transformRequest(filepath, filepath, transformMode)
// TODO: error cannot parse
const testFilepath = relative(ctx.config.root, filepath)
if (!request) {
Expand Down
1 change: 1 addition & 0 deletions src/worker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface WorkerRunnerOptions {
type: 'init'
meta: WorkerMeta
debug: boolean
astCollect: boolean
}

export interface EventReady {
Expand Down
42 changes: 24 additions & 18 deletions src/worker/vitest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export class Vitest implements VitestMethods {
constructor(
public readonly ctx: VitestCore,
private readonly debug = false,
public readonly alwaysAstCollect = false,
) {
this.watcher = new VitestWatcher(this)
this.coverage = new VitestCoverage(ctx, this)
Expand All @@ -34,45 +35,50 @@ export class Vitest implements VitestMethods {
}

public async collectTests(files: [projectName: string, filepath: string][]) {
const browserTests: [project: WorkspaceProject, filepath: string][] = []
const astCollect: [project: WorkspaceProject, filepath: string][] = []
const otherTests: [project: WorkspaceProject, filepath: string][] = []

for (const [projectName, filepath] of files) {
const project = this.ctx.projects.find(project => project.getName() === projectName)
assert(project, `Project ${projectName} not found for file ${filepath}`)
if (project.config.browser.enabled) {
browserTests.push([project, filepath])
if (this.alwaysAstCollect || project.config.browser.enabled) {
astCollect.push([project, filepath])
}
else {
otherTests.push([project, filepath])
}
}

if (browserTests.length) {
await this.astCollect(browserTests)
}

if (otherTests.length) {
const files = otherTests.map(([_, filepath]) => filepath)
await Promise.all([
(async () => {
if (astCollect.length) {
await this.astCollect(astCollect, 'web')
}
})(),
(async () => {
if (otherTests.length) {
const files = otherTests.map(([_, filepath]) => filepath)

try {
await this.runTestFiles(files, Vitest.COLLECT_NAME_PATTERN)
}
finally {
this.setTestNamePattern(undefined)
}
}
try {
await this.runTestFiles(files, Vitest.COLLECT_NAME_PATTERN)
}
finally {
this.setTestNamePattern(undefined)
}
}
})(),
])
}

public async astCollect(specs: [project: WorkspaceProject, file: string][]) {
public async astCollect(specs: [project: WorkspaceProject, file: string][], transformMode: 'web' | 'ssr') {
if (!specs.length) {
return
}

const runConcurrently = limitConcurrency(5)

const promises = specs.map(([project, filename]) => runConcurrently(
() => astCollectTests(project, filename),
() => astCollectTests(project, filename, transformMode),
))
const result = await Promise.all(promises)
const files = result.filter(r => r != null).map((r => r!.file))
Expand Down
10 changes: 5 additions & 5 deletions src/worker/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,20 @@ export class VitestWatcher {

const tests = Array.from(this.changedTests)
const specs = tests.flatMap(file => this.getProjectsByTestFile(file))
const browserSpecs: [project: WorkspaceProject, file: string][] = []
const astSpecs: [project: WorkspaceProject, file: string][] = []

for (const [project, file] of specs) {
if (project.config.browser.enabled) {
browserSpecs.push([project, file])
if (vitest.alwaysAstCollect || project.config.browser.enabled) {
astSpecs.push([project, file])
}
}

ctx.configOverride.testNamePattern = new RegExp(Vitest.COLLECT_NAME_PATTERN)
ctx.logger.log('Collecting tests due to file changes:', ...files.map(f => relative(ctx.config.root, f)))

if (browserSpecs.length) {
if (astSpecs.length) {
ctx.logger.log('Collecting using AST explorer...')
await vitest.astCollect(browserSpecs)
await vitest.astCollect(astSpecs, 'web')
this.changedTests.clear()
return await originalScheduleRerun.call(this, [])
}
Expand Down
2 changes: 1 addition & 1 deletion src/worker/worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ emitter.on('message', async function onMessage(message: any) {
: {},
)

const rpc = createWorkerRPC(new Vitest(vitest, data.debug), {
const rpc = createWorkerRPC(new Vitest(vitest, data.debug, data.astCollect), {
on(listener) {
emitter.on('message', listener)
},
Expand Down
Loading