diff --git a/README.md b/README.md index dba32fad..dba5fe4c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ - **Run**, **debug**, and **watch** Vitest tests in Visual Studio Code. - **Coverage** support (requires VS Code >= 1.88) - An `@open` tag can be used when filtering tests, to only show the tests open in the editor. +- **Inline console.log display**: Console logs appear inline in the editor next to the code that produced them ## Requirements diff --git a/packages/extension/src/runner.ts b/packages/extension/src/runner.ts index c388efb5..37405f2b 100644 --- a/packages/extension/src/runner.ts +++ b/packages/extension/src/runner.ts @@ -153,18 +153,29 @@ export class TestRunner extends vscode.Disposable { this.endTestRun() }) - api.onConsoleLog(({ content, taskId }) => { - const testItem = taskId ? tree.getTestItemByTaskId(taskId) : undefined + api.onConsoleLog((consoleLog) => { + const testItem = consoleLog.taskId ? tree.getTestItemByTaskId(consoleLog.taskId) : undefined const testRun = this.testRun if (testRun) { + // Create location from parsed console log for inline display + let location: vscode.Location | undefined + if (consoleLog.parsedLocation) { + const uri = vscode.Uri.file(consoleLog.parsedLocation.file) + const position = new vscode.Position( + consoleLog.parsedLocation.line, + consoleLog.parsedLocation.column, + ) + location = new vscode.Location(uri, position) + } + testRun.appendOutput( - formatTestOutput(content), - undefined, + formatTestOutput(consoleLog.content) + (consoleLog.browser ? '\r\n' : ''), + location, testItem, ) } else { - log.info('[TEST]', content) + log.info('[TEST]', consoleLog.content) } }) } @@ -765,7 +776,7 @@ function formatTestPattern(tests: readonly vscode.TestItem[]) { } function formatTestOutput(output: string) { - return output.replace(/(? Promise collectTests: (testFile: ExtensionTestSpecification[]) => Promise @@ -54,7 +63,7 @@ export interface ExtensionWorkerTransport { } export interface ExtensionWorkerEvents { - onConsoleLog: (log: UserConsoleLog) => void + onConsoleLog: (log: ExtensionUserConsoleLog) => void onTaskUpdate: (task: RunnerTaskResultPack[]) => void onTestRunEnd: (files: RunnerTestFile[], unhandledError: string, collecting?: boolean) => void onCollected: (file: RunnerTestFile, collecting?: boolean) => void diff --git a/packages/worker-legacy/src/index.ts b/packages/worker-legacy/src/index.ts index 6aebdf33..60eb863b 100644 --- a/packages/worker-legacy/src/index.ts +++ b/packages/worker-legacy/src/index.ts @@ -129,6 +129,9 @@ export async function initVitest( } }, configureVitest(context) { + // Enable printConsoleTrace for inline console log display + context.project.config.printConsoleTrace = true + const options = context.project.config.browser if (options?.enabled && typeof data.debug === 'object') { context.project.config.setupFiles.push(meta.setupFilePaths.browserDebug) diff --git a/packages/worker-legacy/src/reporter.ts b/packages/worker-legacy/src/reporter.ts index 88484ec3..63b50016 100644 --- a/packages/worker-legacy/src/reporter.ts +++ b/packages/worker-legacy/src/reporter.ts @@ -100,7 +100,47 @@ export class VSCodeReporter implements Reporter { } onUserConsoleLog(log: UserConsoleLog) { - this.rpc.onConsoleLog(log) + // Parse stack trace to extract file location for inline display + const extendedLog = log as any + if (log.origin) { + try { + const stacks = this.parseStackTrace({ stack: log.origin } as any, log.taskId) + if (stacks && stacks.length > 0) { + const firstStack = stacks[0] + if (firstStack.file && firstStack.line != null && firstStack.column != null) { + extendedLog.parsedLocation = { + file: firstStack.file, + line: firstStack.line - 1, // Convert to 0-based + column: firstStack.column, + } + } + } + } + catch { + // If parsing fails, continue without parsed location + } + } + this.rpc.onConsoleLog(extendedLog) + } + + parseStackTrace(obj: unknown, taskId: string | undefined) { + const project = taskId + ? this.vitest.getProjectByTaskId(taskId) + : this.vitest.getCoreWorkspaceProject() + + // the new version uses browser.parseErrorStacktrace + if ('getBrowserSourceMapModuleById' in project) { + return parseErrorStacktrace(obj as Error, { + getSourceMap: file => (project as any).getBrowserSourceMapModuleById(file), + }) + } + + const task = taskId && this.vitest.state.idMap.get(taskId) + const isBrowser = task && task.file?.pool === 'browser' + + return isBrowser + ? project.browser?.parseErrorStacktrace(obj) + : parseErrorStacktrace(obj as Error) } onTaskUpdate(packs: TaskResultPack[]) { @@ -111,26 +151,18 @@ export class VSCodeReporter implements Reporter { if ('getBrowserSourceMapModuleById' in project) { result?.errors?.forEach((error) => { if (typeof error === 'object' && error) { - error.stacks = parseErrorStacktrace(error, { - getSourceMap: file => (project as any).getBrowserSourceMapModuleById(file), - }) + error.stacks = this.parseStackTrace(error, taskId) } }) return } - const task = this.vitest.state.idMap.get(taskId) - const isBrowser = task && task.file?.pool === 'browser' - result?.errors?.forEach((error) => { if (isPrimitive(error)) { return } - const stacks = isBrowser - ? project.browser?.parseErrorStacktrace(error) - : parseErrorStacktrace(error) - error.stacks = stacks + error.stacks = this.parseStackTrace(error, taskId) }) }) diff --git a/packages/worker/src/index.ts b/packages/worker/src/index.ts index 5e400fe9..158fcac7 100644 --- a/packages/worker/src/index.ts +++ b/packages/worker/src/index.ts @@ -120,6 +120,7 @@ export async function initVitest( ] return { test: { + printConsoleTrace: true, coverage: { reportOnFailure: true, reportsDirectory: join(tmpdir(), `vitest-coverage-${randomUUID()}`), diff --git a/packages/worker/src/reporter.ts b/packages/worker/src/reporter.ts index 5023dbff..79aa664a 100644 --- a/packages/worker/src/reporter.ts +++ b/packages/worker/src/reporter.ts @@ -10,6 +10,7 @@ import type { Vite, Vitest as VitestCore, } from 'vitest/node' +import { parseErrorStacktrace } from '@vitest/utils/source-map' import { ExtensionWorker } from './worker' interface VSCodeReporterOptions { @@ -59,7 +60,34 @@ export class VSCodeReporter implements Reporter { } onUserConsoleLog(log: UserConsoleLog) { - this.rpc.onConsoleLog(log) + // Parse stack trace to extract file location for inline display + const extendedLog = log as any + if (log.origin) { + try { + const task = log.taskId ? this.vitest.state.idMap.get(log.taskId) : null + const project = task + ? this.vitest.state.getReportedEntity(task)!.project + : this.vitest.getRootProject() + const stacks = log.browser + ? project.browser?.parseErrorStacktrace({ stack: log.origin } as any) + : parseErrorStacktrace({ stack: log.origin } as any) + + if (stacks && stacks.length > 0) { + const firstStack = stacks[0] + if (firstStack.file && firstStack.line != null && firstStack.column != null) { + extendedLog.parsedLocation = { + file: firstStack.file, + line: firstStack.line - 1, // Convert to 0-based + column: firstStack.column, + } + } + } + } + catch { + // If parsing fails, continue without parsed location + } + } + this.rpc.onConsoleLog(extendedLog) } onTaskUpdate(packs: RunnerTaskResultPack[]) { diff --git a/tsconfig.tsbuildinfo b/tsconfig.tsbuildinfo index ceda5908..74aeba30 100644 --- a/tsconfig.tsbuildinfo +++ b/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./debug-shims.d.ts","./packages/extension/src/api.ts","./packages/extension/src/config.ts","./packages/extension/src/constants.ts","./packages/extension/src/coverage.ts","./packages/extension/src/debug.ts","./packages/extension/src/diagnostic.ts","./packages/extension/src/extension.ts","./packages/extension/src/log.ts","./packages/extension/src/polyfills.ts","./packages/extension/src/runner.ts","./packages/extension/src/tagsmanager.ts","./packages/extension/src/testtree.ts","./packages/extension/src/testtreedata.ts","./packages/extension/src/utils.ts","./packages/extension/src/watcher.ts","./packages/extension/src/api/child_process.ts","./packages/extension/src/api/pkg.ts","./packages/extension/src/api/resolve.ts","./packages/extension/src/api/rpc.ts","./packages/extension/src/api/terminal.ts","./packages/extension/src/api/types.ts","./packages/extension/src/api/ws.ts","./packages/extension/src/worker/index.ts","./packages/extension/src/worker/setupfile.ts","./packages/shared/src/emitter.ts","./packages/shared/src/index.ts","./packages/shared/src/rpc.ts","./packages/shared/src/utils.ts","./packages/worker/src/coverage.ts","./packages/worker/src/index.ts","./packages/worker/src/reporter.ts","./packages/worker/src/runner.ts","./packages/worker/src/watcher.ts","./packages/worker/src/worker.ts","./packages/worker-legacy/src/collect.ts","./packages/worker-legacy/src/coverage.ts","./packages/worker-legacy/src/index.ts","./packages/worker-legacy/src/reporter.ts","./packages/worker-legacy/src/types.ts","./packages/worker-legacy/src/watcher.ts","./packages/worker-legacy/src/worker.ts"],"version":"5.8.3"} \ No newline at end of file +{"root":["./debug-shims.d.ts","./packages/extension/src/api.ts","./packages/extension/src/config.ts","./packages/extension/src/constants.ts","./packages/extension/src/coverage.ts","./packages/extension/src/debug.ts","./packages/extension/src/diagnostic.ts","./packages/extension/src/extension.ts","./packages/extension/src/log.ts","./packages/extension/src/polyfills.ts","./packages/extension/src/runner.ts","./packages/extension/src/tagsManager.ts","./packages/extension/src/testTree.ts","./packages/extension/src/testTreeData.ts","./packages/extension/src/utils.ts","./packages/extension/src/watcher.ts","./packages/extension/src/api/child_process.ts","./packages/extension/src/api/pkg.ts","./packages/extension/src/api/resolve.ts","./packages/extension/src/api/rpc.ts","./packages/extension/src/api/terminal.ts","./packages/extension/src/api/types.ts","./packages/extension/src/api/ws.ts","./packages/extension/src/worker/browserSetupFile.ts","./packages/extension/src/worker/index.ts","./packages/shared/src/emitter.ts","./packages/shared/src/index.ts","./packages/shared/src/rpc.ts","./packages/shared/src/utils.ts","./packages/worker/src/coverage.ts","./packages/worker/src/index.ts","./packages/worker/src/reporter.ts","./packages/worker/src/runner.ts","./packages/worker/src/watcher.ts","./packages/worker/src/worker.ts","./packages/worker-legacy/src/collect.ts","./packages/worker-legacy/src/coverage.ts","./packages/worker-legacy/src/index.ts","./packages/worker-legacy/src/reporter.ts","./packages/worker-legacy/src/setupFile.ts","./packages/worker-legacy/src/types.ts","./packages/worker-legacy/src/watcher.ts","./packages/worker-legacy/src/worker.ts"],"version":"5.8.3"} \ No newline at end of file