Skip to content

Commit 3083dea

Browse files
Copilotsheremet-va
andauthored
feat: add inline console.log display feature (#668)
Co-authored-by: sheremet-va <16173870+sheremet-va@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent 00ae9dd commit 3083dea

File tree

8 files changed

+105
-20
lines changed

8 files changed

+105
-20
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- **Run**, **debug**, and **watch** Vitest tests in Visual Studio Code.
1212
- **Coverage** support (requires VS Code >= 1.88)
1313
- An `@open` tag can be used when filtering tests, to only show the tests open in the editor.
14+
- **Inline console.log display**: Console logs appear inline in the editor next to the code that produced them
1415

1516
## Requirements
1617

packages/extension/src/runner.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,18 +153,29 @@ export class TestRunner extends vscode.Disposable {
153153
this.endTestRun()
154154
})
155155

156-
api.onConsoleLog(({ content, taskId }) => {
157-
const testItem = taskId ? tree.getTestItemByTaskId(taskId) : undefined
156+
api.onConsoleLog((consoleLog) => {
157+
const testItem = consoleLog.taskId ? tree.getTestItemByTaskId(consoleLog.taskId) : undefined
158158
const testRun = this.testRun
159159
if (testRun) {
160+
// Create location from parsed console log for inline display
161+
let location: vscode.Location | undefined
162+
if (consoleLog.parsedLocation) {
163+
const uri = vscode.Uri.file(consoleLog.parsedLocation.file)
164+
const position = new vscode.Position(
165+
consoleLog.parsedLocation.line,
166+
consoleLog.parsedLocation.column,
167+
)
168+
location = new vscode.Location(uri, position)
169+
}
170+
160171
testRun.appendOutput(
161-
formatTestOutput(content),
162-
undefined,
172+
formatTestOutput(consoleLog.content) + (consoleLog.browser ? '\r\n' : ''),
173+
location,
163174
testItem,
164175
)
165176
}
166177
else {
167-
log.info('[TEST]', content)
178+
log.info('[TEST]', consoleLog.content)
168179
}
169180
})
170181
}
@@ -765,7 +776,7 @@ function formatTestPattern(tests: readonly vscode.TestItem[]) {
765776
}
766777

767778
function formatTestOutput(output: string) {
768-
return output.replace(/(?<!\r)\n/g, '\r\n')
779+
return stripVTControlCharacters(output.replace(/(?<!\r)\n/g, '\r\n'))
769780
}
770781

771782
function labelTestItems(items: readonly vscode.TestItem[] | undefined) {

packages/shared/src/index.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ export type ExtensionTestFileSpecification = [
2828
ExtensionTestFileMetadata,
2929
]
3030

31+
export interface ExtensionUserConsoleLog extends UserConsoleLog {
32+
// Parsed location from stack trace for inline display
33+
parsedLocation?: {
34+
file: string
35+
line: number // 0-based line number
36+
column: number
37+
}
38+
}
39+
3140
export interface ExtensionWorkerTransport {
3241
getFiles: () => Promise<ExtensionTestFileSpecification[]>
3342
collectTests: (testFile: ExtensionTestSpecification[]) => Promise<void>
@@ -54,7 +63,7 @@ export interface ExtensionWorkerTransport {
5463
}
5564

5665
export interface ExtensionWorkerEvents {
57-
onConsoleLog: (log: UserConsoleLog) => void
66+
onConsoleLog: (log: ExtensionUserConsoleLog) => void
5867
onTaskUpdate: (task: RunnerTaskResultPack[]) => void
5968
onTestRunEnd: (files: RunnerTestFile[], unhandledError: string, collecting?: boolean) => void
6069
onCollected: (file: RunnerTestFile, collecting?: boolean) => void

packages/worker-legacy/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ export async function initVitest(
129129
}
130130
},
131131
configureVitest(context) {
132+
// Enable printConsoleTrace for inline console log display
133+
context.project.config.printConsoleTrace = true
134+
132135
const options = context.project.config.browser
133136
if (options?.enabled && typeof data.debug === 'object') {
134137
context.project.config.setupFiles.push(meta.setupFilePaths.browserDebug)

packages/worker-legacy/src/reporter.ts

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,47 @@ export class VSCodeReporter implements Reporter {
100100
}
101101

102102
onUserConsoleLog(log: UserConsoleLog) {
103-
this.rpc.onConsoleLog(log)
103+
// Parse stack trace to extract file location for inline display
104+
const extendedLog = log as any
105+
if (log.origin) {
106+
try {
107+
const stacks = this.parseStackTrace({ stack: log.origin } as any, log.taskId)
108+
if (stacks && stacks.length > 0) {
109+
const firstStack = stacks[0]
110+
if (firstStack.file && firstStack.line != null && firstStack.column != null) {
111+
extendedLog.parsedLocation = {
112+
file: firstStack.file,
113+
line: firstStack.line - 1, // Convert to 0-based
114+
column: firstStack.column,
115+
}
116+
}
117+
}
118+
}
119+
catch {
120+
// If parsing fails, continue without parsed location
121+
}
122+
}
123+
this.rpc.onConsoleLog(extendedLog)
124+
}
125+
126+
parseStackTrace(obj: unknown, taskId: string | undefined) {
127+
const project = taskId
128+
? this.vitest.getProjectByTaskId(taskId)
129+
: this.vitest.getCoreWorkspaceProject()
130+
131+
// the new version uses browser.parseErrorStacktrace
132+
if ('getBrowserSourceMapModuleById' in project) {
133+
return parseErrorStacktrace(obj as Error, {
134+
getSourceMap: file => (project as any).getBrowserSourceMapModuleById(file),
135+
})
136+
}
137+
138+
const task = taskId && this.vitest.state.idMap.get(taskId)
139+
const isBrowser = task && task.file?.pool === 'browser'
140+
141+
return isBrowser
142+
? project.browser?.parseErrorStacktrace(obj)
143+
: parseErrorStacktrace(obj as Error)
104144
}
105145

106146
onTaskUpdate(packs: TaskResultPack[]) {
@@ -111,26 +151,18 @@ export class VSCodeReporter implements Reporter {
111151
if ('getBrowserSourceMapModuleById' in project) {
112152
result?.errors?.forEach((error) => {
113153
if (typeof error === 'object' && error) {
114-
error.stacks = parseErrorStacktrace(error, {
115-
getSourceMap: file => (project as any).getBrowserSourceMapModuleById(file),
116-
})
154+
error.stacks = this.parseStackTrace(error, taskId)
117155
}
118156
})
119157
return
120158
}
121159

122-
const task = this.vitest.state.idMap.get(taskId)
123-
const isBrowser = task && task.file?.pool === 'browser'
124-
125160
result?.errors?.forEach((error) => {
126161
if (isPrimitive(error)) {
127162
return
128163
}
129164

130-
const stacks = isBrowser
131-
? project.browser?.parseErrorStacktrace(error)
132-
: parseErrorStacktrace(error)
133-
error.stacks = stacks
165+
error.stacks = this.parseStackTrace(error, taskId)
134166
})
135167
})
136168

packages/worker/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ export async function initVitest(
120120
]
121121
return {
122122
test: {
123+
printConsoleTrace: true,
123124
coverage: {
124125
reportOnFailure: true,
125126
reportsDirectory: join(tmpdir(), `vitest-coverage-${randomUUID()}`),

packages/worker/src/reporter.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {
1010
Vite,
1111
Vitest as VitestCore,
1212
} from 'vitest/node'
13+
import { parseErrorStacktrace } from '@vitest/utils/source-map'
1314
import { ExtensionWorker } from './worker'
1415

1516
interface VSCodeReporterOptions {
@@ -59,7 +60,34 @@ export class VSCodeReporter implements Reporter {
5960
}
6061

6162
onUserConsoleLog(log: UserConsoleLog) {
62-
this.rpc.onConsoleLog(log)
63+
// Parse stack trace to extract file location for inline display
64+
const extendedLog = log as any
65+
if (log.origin) {
66+
try {
67+
const task = log.taskId ? this.vitest.state.idMap.get(log.taskId) : null
68+
const project = task
69+
? this.vitest.state.getReportedEntity(task)!.project
70+
: this.vitest.getRootProject()
71+
const stacks = log.browser
72+
? project.browser?.parseErrorStacktrace({ stack: log.origin } as any)
73+
: parseErrorStacktrace({ stack: log.origin } as any)
74+
75+
if (stacks && stacks.length > 0) {
76+
const firstStack = stacks[0]
77+
if (firstStack.file && firstStack.line != null && firstStack.column != null) {
78+
extendedLog.parsedLocation = {
79+
file: firstStack.file,
80+
line: firstStack.line - 1, // Convert to 0-based
81+
column: firstStack.column,
82+
}
83+
}
84+
}
85+
}
86+
catch {
87+
// If parsing fails, continue without parsed location
88+
}
89+
}
90+
this.rpc.onConsoleLog(extendedLog)
6391
}
6492

6593
onTaskUpdate(packs: RunnerTaskResultPack[]) {

tsconfig.tsbuildinfo

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +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"}
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/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"}

0 commit comments

Comments
 (0)