Skip to content

Commit ef77dcc

Browse files
authoredDec 28, 2022
fix(api): make api parse error stacks and return sourcePos in onTaskUpdate (#2563)
* fix(api): make api parse error stacks on onTaskUpdate * chore(node): remove all references to sourcePos * chore(node): remove Position type
1 parent b5dd8bc commit ef77dcc

File tree

11 files changed

+49
-87
lines changed

11 files changed

+49
-87
lines changed
 

‎packages/ui/client/components/views/ViewEditor.vue

+5-5
Original file line numberDiff line numberDiff line change
@@ -79,12 +79,12 @@ watch([cm, failed], ([cmValue]) => {
7979
const e = i.result?.error
8080
const stacks = (e?.stacks || []).filter(i => i.file && i.file === props.file?.filepath)
8181
if (stacks.length) {
82-
const pos = stacks[0].sourcePos || stacks[0]
82+
const stack = stacks[0]
8383
const div = document.createElement('div')
8484
div.className = 'op80 flex gap-x-2 items-center'
8585
const pre = document.createElement('pre')
8686
pre.className = 'c-red-600 dark:c-red-400'
87-
pre.textContent = `${' '.repeat(pos.column)}^ ${e?.nameStr}: ${e?.message}`
87+
pre.textContent = `${' '.repeat(stack.column)}^ ${e?.nameStr}: ${e?.message}`
8888
div.appendChild(pre)
8989
const span = document.createElement('span')
9090
span.className = 'i-carbon-launch c-red-600 dark:c-red-400 hover:cursor-pointer min-w-1em min-h-1em'
@@ -95,12 +95,12 @@ watch([cm, failed], ([cmValue]) => {
9595
placement: 'bottom',
9696
}, false)
9797
const el: EventListener = async () => {
98-
await openInEditor(stacks[0].file, pos.line, pos.column)
98+
await openInEditor(stacks[0].file, stack.line, stack.column)
9999
}
100100
div.appendChild(span)
101101
listeners.push([span, el, () => destroyTooltip(span)])
102-
handles.push(cm.value!.addLineClass(pos.line - 1, 'wrap', 'bg-red-500/10'))
103-
widgets.push(cm.value!.addLineWidget(pos.line - 1, div))
102+
handles.push(cm.value!.addLineClass(stack.line - 1, 'wrap', 'bg-red-500/10'))
103+
widgets.push(cm.value!.addLineWidget(stack.line - 1, div))
104104
}
105105
})
106106
if (!hasBeenEdited.value)

‎packages/ui/client/components/views/ViewReport.cy.tsx

+2-7
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,6 @@ const stackRowSelector = '[data-testid=stack]'
99
const makeTextStack = () => ({
1010
line: faker.datatype.number({ min: 0, max: 120 }),
1111
column: faker.datatype.number({ min: 0, max: 5000 }),
12-
sourcePos: {
13-
line: faker.datatype.number({ min: 121, max: 240 }),
14-
column: faker.datatype.number({ min: 5001, max: 10000 }),
15-
},
1612
// Absolute file paths
1713
file: faker.system.filePath(),
1814
method: faker.hacker.verb(),
@@ -49,9 +45,8 @@ describe('ViewReport', () => {
4945
cy.get(stackRowSelector).should('have.length', stacks.length)
5046
.get(stackRowSelector)
5147
.each(($stack, idx) => {
52-
const { column, line, file: fileName, sourcePos } = stacks[idx]
53-
expect($stack).not.to.contain.text(`${line}:${column}`)
54-
expect($stack).to.contain.text(`${sourcePos.line}:${sourcePos.column}`)
48+
const { column, line, file: fileName } = stacks[idx]
49+
expect($stack).to.contain.text(`${line}:${column}`)
5550
expect($stack).to.contain.text(`- ${fileName}`)
5651
})
5752
})

‎packages/ui/client/components/views/ViewReport.vue

+2-10
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,6 @@ function relative(p: string) {
8888
return p
8989
}
9090
91-
function line(stack: ParsedStack) {
92-
return stack.sourcePos?.line ?? stack.line
93-
}
94-
95-
function column(stack: ParsedStack) {
96-
return stack.sourcePos?.column ?? stack.column
97-
}
98-
9991
interface Diff { error: NonNullable<Pick<ErrorWithDiff, 'expected' | 'actual'>> }
10092
type ResultWithDiff = Task['result'] & Diff
10193
function isDiffShowable(result?: Task['result']): result is ResultWithDiff {
@@ -128,14 +120,14 @@ function diff(result: ResultWithDiff): string {
128120
<div v-else-if="task.result?.error" class="scrolls scrolls-rounded task-error">
129121
<pre><b>{{ task.result.error.name || task.result.error.nameStr }}</b>: {{ task.result.error.message }}</pre>
130122
<div v-for="(stack, i) of task.result.error.stacks" :key="i" class="op80 flex gap-x-2 items-center" data-testid="stack">
131-
<pre> - {{ relative(stack.file) }}:{{ line(stack) }}:{{ column(stack) }}</pre>
123+
<pre> - {{ relative(stack.file) }}:{{ stack.line }}:{{ stack.column }}</pre>
132124
<div
133125
v-if="shouldOpenInEditor(stack.file, props.file?.name)"
134126
v-tooltip.bottom="'Open in Editor'"
135127
class="i-carbon-launch c-red-600 dark:c-red-400 hover:cursor-pointer min-w-1em min-h-1em"
136128
tabindex="0"
137129
aria-label="Open in Editor"
138-
@click.passive="openInEditor(stack.file, line(stack), column(stack))"
130+
@click.passive="openInEditor(stack.file, stack.line, stack.column)"
139131
/>
140132
</div>
141133
<pre v-if="isDiffShowable(task.result)">

‎packages/vitest/src/api/setup.ts

+7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { promises as fs } from 'node:fs'
2+
23
import type { BirpcReturn } from 'birpc'
34
import { createBirpc } from 'birpc'
45
import { parse, stringify } from 'flatted'
@@ -8,6 +9,7 @@ import { API_PATH } from '../constants'
89
import type { Vitest } from '../node'
910
import type { File, ModuleGraphData, Reporter, TaskResultPack, UserConsoleLog } from '../types'
1011
import { getModuleGraph } from '../utils'
12+
import { parseStacktrace } from '../utils/source-map'
1113
import type { TransformResultWithSource, WebSocketEvents, WebSocketHandlers } from './types'
1214

1315
export function setup(ctx: Vitest) {
@@ -121,6 +123,11 @@ class WebSocketReporter implements Reporter {
121123
if (this.clients.size === 0)
122124
return
123125

126+
packs.forEach(([, result]) => {
127+
if (result?.error)
128+
result.error.stacks = parseStacktrace(result.error)
129+
})
130+
124131
this.clients.forEach((client) => {
125132
client.onTaskUpdate?.(packs)
126133
})

‎packages/vitest/src/integrations/snapshot/port/inlineSnapshot.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { promises as fs } from 'fs'
22
import type MagicString from 'magic-string'
3-
import { lineSplitRE, numberToPos, posToNumber } from '../../../utils/source-map'
3+
import { lineSplitRE, offsetToLineNumber, positionToOffset } from '../../../utils/source-map'
44
import { getCallLastIndex } from '../../../utils'
55

66
export interface InlineSnapshot {
@@ -21,7 +21,7 @@ export async function saveInlineSnapshots(
2121
const s = new MagicString(code)
2222

2323
for (const snap of snaps) {
24-
const index = posToNumber(code, snap)
24+
const index = positionToOffset(code, snap.line, snap.column)
2525
replaceInlineSnap(code, s, index, snap.snapshot)
2626
}
2727

@@ -50,8 +50,8 @@ function replaceObjectSnap(code: string, s: MagicString, index: number, newSnap:
5050
}
5151

5252
function prepareSnapString(snap: string, source: string, index: number) {
53-
const lineIndex = numberToPos(source, index).line
54-
const line = source.split(lineSplitRE)[lineIndex - 1]
53+
const lineNumber = offsetToLineNumber(source, index)
54+
const line = source.split(lineSplitRE)[lineNumber - 1]
5555
const indent = line.match(/^\s*/)![0] || ''
5656
const indentNext = indent.includes('\t') ? `${indent}\t` : `${indent} `
5757

‎packages/vitest/src/node/error.ts

+14-27
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/* eslint-disable prefer-template */
22
import { existsSync, readFileSync } from 'fs'
3-
import { join, normalize, relative } from 'pathe'
3+
import { normalize, relative } from 'pathe'
44
import c from 'picocolors'
55
import cliTruncate from 'cli-truncate'
6-
import type { ErrorWithDiff, ParsedStack, Position } from '../types'
7-
import { lineSplitRE, parseStacktrace, posToNumber } from '../utils/source-map'
6+
import type { ErrorWithDiff, ParsedStack } from '../types'
7+
import { lineSplitRE, parseStacktrace, positionToOffset } from '../utils/source-map'
88
import { F_POINTER } from '../utils/figures'
99
import { stringify } from '../integrations/chai/jest-matcher-utils'
1010
import { TypeCheckError } from '../typecheck/typechecker'
@@ -13,12 +13,6 @@ import type { Vitest } from './core'
1313
import { divider } from './reporters/renderers/utils'
1414
import type { Logger } from './logger'
1515

16-
export function fileFromParsedStack(stack: ParsedStack) {
17-
if (stack?.sourcePos?.source?.startsWith('..'))
18-
return join(stack.file, '../', stack.sourcePos.source)
19-
return stack.file
20-
}
21-
2216
interface PrintErrorOptions {
2317
type?: string
2418
fullStack?: boolean
@@ -64,15 +58,10 @@ export async function printError(error: unknown, ctx: Vitest, options: PrintErro
6458
ctx.logger.error(c.yellow(e.frame))
6559
}
6660
else {
67-
printStack(ctx, stacks, nearest, errorProperties, (s, pos) => {
61+
printStack(ctx, stacks, nearest, errorProperties, (s) => {
6862
if (showCodeFrame && s === nearest && nearest) {
69-
const file = fileFromParsedStack(nearest)
70-
// could point to non-existing original file
71-
// for example, when there is a source map file, but no source in node_modules
72-
if (nearest.file === file || existsSync(file)) {
73-
const sourceCode = readFileSync(file, 'utf-8')
74-
ctx.logger.error(c.yellow(generateCodeFrame(sourceCode, 4, pos)))
75-
}
63+
const sourceCode = readFileSync(nearest.file, 'utf-8')
64+
ctx.logger.error(c.yellow(generateCodeFrame(sourceCode, 4, s.line, s.column)))
7665
}
7766
})
7867
}
@@ -181,21 +170,19 @@ function printStack(
181170
stack: ParsedStack[],
182171
highlight: ParsedStack | undefined,
183172
errorProperties: Record<string, unknown>,
184-
onStack?: ((stack: ParsedStack, pos: Position) => void),
173+
onStack?: ((stack: ParsedStack) => void),
185174
) {
186175
if (!stack.length)
187176
return
188177

189178
const logger = ctx.logger
190179

191180
for (const frame of stack) {
192-
const pos = frame.sourcePos || frame
193181
const color = frame === highlight ? c.yellow : c.gray
194-
const file = fileFromParsedStack(frame)
195-
const path = relative(ctx.config.root, file)
182+
const path = relative(ctx.config.root, frame.file)
196183

197-
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, c.dim(`${path}:${pos.line}:${pos.column}`)].filter(Boolean).join(' ')}`))
198-
onStack?.(frame, pos)
184+
logger.error(color(` ${c.dim(F_POINTER)} ${[frame.method, c.dim(`${path}:${frame.line}:${frame.column}`)].filter(Boolean).join(' ')}`))
185+
onStack?.(frame)
199186

200187
// reached at test file, skip the follow stack
201188
if (frame.file in ctx.state.filesMap)
@@ -213,12 +200,12 @@ function printStack(
213200
export function generateCodeFrame(
214201
source: string,
215202
indent = 0,
216-
start: number | Position = 0,
217-
end?: number,
203+
lineNumber: number,
204+
columnNumber: number,
218205
range = 2,
219206
): string {
220-
start = posToNumber(source, start)
221-
end = end || start
207+
const start = positionToOffset(source, lineNumber, columnNumber)
208+
const end = start
222209
const lines = source.split(lineSplitRE)
223210
let count = 0
224211
let res: string[] = []

‎packages/vitest/src/node/reporters/json.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,6 @@ export class JsonReporter implements Reporter {
188188
if (!frame)
189189
return
190190

191-
const pos = frame.sourcePos || frame
192-
return { line: pos.line, column: pos.column }
191+
return { line: frame.line, column: frame.column }
193192
}
194193
}

‎packages/vitest/src/node/reporters/junit.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,9 @@ export class JUnitReporter implements Reporter {
127127

128128
// TODO: This is same as printStack but without colors. Find a way to reuse code.
129129
for (const frame of stack) {
130-
const pos = frame.sourcePos ?? frame
131130
const path = relative(this.ctx.config.root, frame.file)
132131

133-
await this.baseLog(` ${F_POINTER} ${[frame.method, `${path}:${pos.line}:${pos.column}`].filter(Boolean).join(' ')}`)
132+
await this.baseLog(` ${F_POINTER} ${[frame.method, `${path}:${frame.line}:${frame.column}`].filter(Boolean).join(' ')}`)
134133

135134
// reached at test file, skip the follow stack
136135
if (frame.file in this.ctx.state.filesMap)

‎packages/vitest/src/typecheck/typechecker.ts

-4
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,6 @@ export class Typechecker {
149149
line: info.line,
150150
column: info.column,
151151
method: '',
152-
sourcePos: {
153-
line: info.line,
154-
column: info.column,
155-
},
156152
},
157153
])
158154
Error.stackTraceLimit = limit

‎packages/vitest/src/types/general.ts

-7
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,11 @@ export interface UserConsoleLog {
4747
size: number
4848
}
4949

50-
export interface Position {
51-
source?: string
52-
line: number
53-
column: number
54-
}
55-
5650
export interface ParsedStack {
5751
method: string
5852
file: string
5953
line: number
6054
column: number
61-
sourcePos?: Position
6255
}
6356

6457
export interface ErrorWithDiff extends Error {

‎packages/vitest/src/utils/source-map.ts

+13-19
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { resolve } from 'pathe'
2-
import type { ErrorWithDiff, ParsedStack, Position } from '../types'
2+
import type { ErrorWithDiff, ParsedStack } from '../types'
33
import { notNullish } from './base'
44

55
export const lineSplitRE = /\r?\n/
@@ -91,31 +91,27 @@ export function parseStacktrace(e: ErrorWithDiff, full = false): ParsedStack[] {
9191
return stackFrames
9292
}
9393

94-
export function posToNumber(
94+
export function positionToOffset(
9595
source: string,
96-
pos: number | Position,
96+
lineNumber: number,
97+
columnNumber: number,
9798
): number {
98-
if (typeof pos === 'number')
99-
return pos
10099
const lines = source.split(lineSplitRE)
101-
const { line, column } = pos
102100
let start = 0
103101

104-
if (line > lines.length)
102+
if (lineNumber > lines.length)
105103
return source.length
106104

107-
for (let i = 0; i < line - 1; i++)
105+
for (let i = 0; i < lineNumber - 1; i++)
108106
start += lines[i].length + 1
109107

110-
return start + column
108+
return start + columnNumber
111109
}
112110

113-
export function numberToPos(
111+
export function offsetToLineNumber(
114112
source: string,
115-
offset: number | Position,
116-
): Position {
117-
if (typeof offset !== 'number')
118-
return offset
113+
offset: number,
114+
): number {
119115
if (offset > source.length) {
120116
throw new Error(
121117
`offset is longer than source length! offset ${offset} > length ${source.length}`,
@@ -124,14 +120,12 @@ export function numberToPos(
124120
const lines = source.split(lineSplitRE)
125121
let counted = 0
126122
let line = 0
127-
let column = 0
128123
for (; line < lines.length; line++) {
129124
const lineLength = lines[line].length + 1
130-
if (counted + lineLength >= offset) {
131-
column = offset - counted + 1
125+
if (counted + lineLength >= offset)
132126
break
133-
}
127+
134128
counted += lineLength
135129
}
136-
return { line: line + 1, column }
130+
return line + 1
137131
}

0 commit comments

Comments
 (0)
Please sign in to comment.