Skip to content

Commit 2851adc

Browse files
authored
feat: introduce "show transformed module" command (#690)
1 parent 21c365d commit 2851adc

File tree

9 files changed

+173
-4
lines changed

9 files changed

+173
-4
lines changed

package.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,24 @@
8080
"title": "Reveal in Test Explorer",
8181
"command": "vitest.revealInTestExplorer",
8282
"category": "Vitest"
83+
},
84+
{
85+
"title": "Open Transformed Module",
86+
"command": "vitest.openTransformedModule",
87+
"category": "Vitest"
8388
}
8489
],
8590
"menus": {
8691
"editor/title/context": [
8792
{
8893
"command": "vitest.revealInTestExplorer",
89-
"when": "vitest.testFiles && resourcePath in vitest.testFiles"
94+
"when": "vitest.testFiles && resourcePath in vitest.testFiles",
95+
"group": "vitest"
96+
},
97+
{
98+
"command": "vitest.openTransformedModule",
99+
"when": "vitest.environmentsSupported",
100+
"group": "vitest"
90101
}
91102
],
92103
"commandPalette": [

packages/extension/src/api.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,17 @@ export class VitestAPI {
4343
return this.api.forEach(callback)
4444
}
4545

46+
getModuleEnvironments(moduleId: string) {
47+
return Promise.all(
48+
this.api.map(async (api) => {
49+
return {
50+
api,
51+
projects: await api.getModuleEnvironments(moduleId),
52+
}
53+
}),
54+
)
55+
}
56+
4657
get folderAPIs() {
4758
return this.api
4859
}
@@ -111,6 +122,14 @@ export class VitestFolderAPI {
111122
return this.pkg
112123
}
113124

125+
getTransformedModule(project: string, environment: string, moduleId: string) {
126+
return this.meta.rpc.getTransformedModule(project, environment, moduleId)
127+
}
128+
129+
async getModuleEnvironments(moduleId: string) {
130+
return this.meta.rpc.getModuleEnvironments(moduleId)
131+
}
132+
114133
async runFiles(specs?: ExtensionTestSpecification[] | string[], testNamePatern?: string) {
115134
await this.meta.rpc.runTests(normalizeSpecs(specs), testNamePatern)
116135
}

packages/extension/src/api/ws.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { ResolvedMeta } from '../api'
44
import type { VitestPackage } from './pkg'
55
import { pathToFileURL } from 'node:url'
66
import { gte } from 'semver'
7+
import vscode from 'vscode'
78
import { getConfig } from '../config'
89
import { browserSetupFilePath, finalCoverageFileName, setupFilePath } from '../constants'
910
import { log } from '../log'
@@ -72,6 +73,13 @@ export function onWsConnection(
7273
log.verbose?.('[API]', 'Vitest WebSocket connection closed, cannot call RPC anymore.')
7374
api.$close()
7475
})
76+
if (!message.legacy) {
77+
vscode.commands.executeCommand(
78+
'setContext',
79+
'vitest.environmentsSupported',
80+
true,
81+
)
82+
}
7583
onStart({
7684
rpc: api,
7785
workspaceSource: message.workspaceSource,

packages/extension/src/extension.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { DebugManager, debugTests } from './debug'
1212
import { ExtensionDiagnostic } from './diagnostic'
1313
import { log } from './log'
1414
import { TestRunner } from './runner'
15+
import { SchemaProvider } from './schemaProvider'
1516
import { TagsManager } from './tagsManager'
1617
import { TestTree } from './testTree'
1718
import { getTestData, TestFile } from './testTreeData'
@@ -41,6 +42,7 @@ class VitestExtension {
4142
private disposables: vscode.Disposable[] = []
4243
private diagnostic: ExtensionDiagnostic | undefined
4344
private debugManager: DebugManager
45+
private schemaProvider: SchemaProvider
4446

4547
/** @internal */
4648
_debugDisposable: vscode.Disposable | undefined
@@ -58,6 +60,12 @@ class VitestExtension {
5860
this.testTree = new TestTree(this.testController, this.loadingTestItem)
5961
this.tagsManager = new TagsManager(this.testTree)
6062
this.debugManager = new DebugManager()
63+
this.schemaProvider = new SchemaProvider(
64+
async (apiId, project, environment, file) => {
65+
const api = this.api?.folderAPIs.find(a => a.id === apiId)
66+
return api?.getTransformedModule(project, environment, file) ?? null
67+
},
68+
)
6169
}
6270

6371
private _defineTestProfilePromise: Promise<void> | undefined
@@ -344,6 +352,51 @@ class VitestExtension {
344352
const tokenSource = new vscode.CancellationTokenSource()
345353
await profile.runHandler(request, tokenSource.token)
346354
}),
355+
vscode.commands.registerCommand('vitest.openTransformedModule', async (uri: vscode.Uri | undefined) => {
356+
const currentUri = uri || vscode.window.activeTextEditor?.document.uri
357+
if (!this.api || !currentUri || currentUri.scheme === 'vitest-transform') {
358+
return
359+
}
360+
const environments = await this.api.getModuleEnvironments(currentUri.fsPath)
361+
const options = environments.map(({ api, projects }) => {
362+
return projects.map((project) => {
363+
return project.environments.map((environment) => {
364+
let label = ''
365+
if (environments.length > 1) {
366+
label += `${api.prefix}: `
367+
}
368+
if (project.name) {
369+
label += `[${project.name}] `
370+
}
371+
label += environment
372+
return {
373+
label,
374+
uriParts: [api.id, project.name, environment],
375+
}
376+
})
377+
})
378+
}).flat(2)
379+
if (options.length === 0) {
380+
vscode.window.showWarningMessage('All module graphs are empty, nothing to show.')
381+
return
382+
}
383+
const pick = options.length === 1 ? options[0] : await vscode.window.showQuickPick(options)
384+
if (!pick) {
385+
return
386+
}
387+
try {
388+
const [apiId, projectName, environment] = pick.uriParts
389+
const uri = vscode.Uri.parse(
390+
`vitest-transform://${currentUri.fsPath}.js?apiId=${apiId}&project=${projectName}&environment=${environment}`,
391+
)
392+
const doc = await vscode.workspace.openTextDocument(uri)
393+
await vscode.window.showTextDocument(doc, { preview: false })
394+
}
395+
catch (err) {
396+
log.error(err)
397+
vscode.window.showErrorMessage(`Vitest: The file was not processed by Vite yet. Try running the tests first${options.length > 1 ? ' or select a different environment' : ''}.`)
398+
}
399+
}),
347400
]
348401

349402
// if the config changes, re-define all test profiles
@@ -429,6 +482,7 @@ class VitestExtension {
429482
this.testTree.dispose()
430483
this.tagsManager.dispose()
431484
this.testController.dispose()
485+
this.schemaProvider.dispose()
432486
this.runProfiles.forEach(profile => profile.dispose())
433487
this.runProfiles.clear()
434488
this.disposables.forEach(d => d.dispose())
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import * as vscode from 'vscode'
2+
3+
export class SchemaProvider implements vscode.TextDocumentContentProvider, vscode.Disposable {
4+
private disposables: vscode.Disposable[] = []
5+
6+
constructor(
7+
private getTransformedModule: (apiId: string, project: string, environment: string, file: string) => Promise<string | null>,
8+
) {
9+
this.disposables.push(vscode.workspace.registerTextDocumentContentProvider('vitest-transform', this))
10+
}
11+
12+
async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
13+
const searchParams = new URLSearchParams(uri.query)
14+
const apiId = searchParams.get('apiId')
15+
const project = searchParams.get('project')
16+
const environment = searchParams.get('environment')
17+
if (apiId == null || project == null || environment == null) {
18+
throw new Error(`Cannot parse the schema: ${uri.toString()}`)
19+
}
20+
const fsPath = uri.fsPath.slice(0, -3) // remove .js
21+
const content = await this.getTransformedModule(apiId, project, environment, fsPath)
22+
if (content == null) {
23+
throw new Error(`The file ${fsPath} was not processed by Vite yet.`)
24+
}
25+
return content
26+
}
27+
28+
dispose() {
29+
this.disposables.forEach(d => d.dispose())
30+
}
31+
}

packages/extension/src/worker/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ emitter.on('message', async function onMessage(message: any) {
2525
pathToFileURL(normalizeDriveLetter(data.meta.vitestNodePath)).toString()
2626
) as typeof import('vitest/node')
2727

28-
const isOld = !vitestModule.version || (Number(vitestModule.version[0]) < 4)
29-
const workerName = isOld ? './workerLegacy.js' : './workerNew.js'
28+
const isLegacy = !vitestModule.version || (Number(vitestModule.version[0]) < 4)
29+
const workerName = isLegacy ? './workerLegacy.js' : './workerNew.js'
3030
const workerPath = pathToFileURL(join(__dirname, workerName))
3131
const initModule = await import(workerPath.toString())
3232

@@ -53,7 +53,7 @@ emitter.on('message', async function onMessage(message: any) {
5353
)
5454
worker.initRpc(rpc)
5555
reporter.initRpc(rpc)
56-
emitter.ready(configs, workspaceSource, isOld)
56+
emitter.ready(configs, workspaceSource, isLegacy)
5757
}
5858
catch (err: any) {
5959
emitter.error(err)

packages/shared/src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,11 @@ export interface ExtensionUserConsoleLog extends UserConsoleLog {
3737
}
3838
}
3939

40+
export interface ExtensionEnvironment {
41+
name: string
42+
environments: string[]
43+
}
44+
4045
export interface ExtensionWorkerTransport {
4146
getFiles: () => Promise<ExtensionTestFileSpecification[]>
4247
collectTests: (testFile: ExtensionTestSpecification[]) => Promise<void>
@@ -58,6 +63,8 @@ export interface ExtensionWorkerTransport {
5863
onFilesChanged: (files: string[]) => void
5964

6065
initRpc: (rpc: VitestWorkerRPC) => void
66+
getModuleEnvironments: (moduleId: string) => ExtensionEnvironment[]
67+
getTransformedModule: (project: string, environment: string, moduleId: string) => string | null
6168

6269
onBrowserDebug: (fulfilled: boolean) => void
6370
}

packages/worker-legacy/src/worker.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,4 +425,13 @@ export class ExtensionWorker implements ExtensionWorkerTransport {
425425
onBrowserDebug(fulfilled: boolean) {
426426
ExtensionWorker.emitter.emit('onBrowserDebug', fulfilled)
427427
}
428+
429+
// TODO:(?) -- if environments are supported
430+
getModuleEnvironments() {
431+
return []
432+
}
433+
434+
getTransformedModule() {
435+
return null
436+
}
428437
}

packages/worker/src/worker.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
ExtensionEnvironment,
23
ExtensionTestFileSpecification,
34
ExtensionTestSpecification,
45
ExtensionWorkerTransport,
@@ -103,6 +104,35 @@ export class ExtensionWorker implements ExtensionWorkerTransport {
103104
this.runner.initRpc(rpc)
104105
}
105106

107+
getModuleEnvironments(moduleId: string): ExtensionEnvironment[] {
108+
return this.vitest.projects.map((project) => {
109+
const environments = new Set<string>()
110+
for (const name in project.vite.environments) {
111+
const environment = project.vite.environments[name]
112+
const nodes = [...environment.moduleGraph.getModulesByFile(moduleId) || []]
113+
if (nodes.some(n => n.transformResult)) {
114+
environments.add(name)
115+
}
116+
}
117+
return {
118+
name: project.name,
119+
environments: Array.from(environments),
120+
}
121+
})
122+
}
123+
124+
getTransformedModule(projectName: string, environmentName: string, moduleId: string) {
125+
const project = this.vitest.projects.find(p => p.name === projectName)
126+
const environment = environmentName === '__browser__'
127+
? project?.browser?.vite?.environments.client
128+
: project?.vite.environments[environmentName]
129+
const files = environment?.moduleGraph.getModulesByFile(moduleId)
130+
if (!files || !files.size) {
131+
return null
132+
}
133+
return files.values().next().value?.transformResult?.code ?? null
134+
}
135+
106136
onBrowserDebug(fulfilled: boolean) {
107137
ExtensionWorker.emitter.emit('onBrowserDebug', fulfilled)
108138
}

0 commit comments

Comments
 (0)