forked from vitest-dev/vitest
/
source-map.ts
126 lines (107 loc) · 3.23 KB
/
source-map.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import { SourceMapConsumer } from 'source-map-js'
import type { RawSourceMap } from 'vite-node'
import type { ErrorWithDiff, ParsedStack, Position } from '../types'
import type { Vitest } from '../node'
import { notNullish, slash } from './base'
export const lineSplitRE = /\r?\n/
export function getOriginalPos(map: RawSourceMap | null | undefined, { line, column }: Position): Promise<Position | null> {
return new Promise((resolve) => {
if (!map)
return resolve(null)
const consumer = new SourceMapConsumer(map)
const pos = consumer.originalPositionFor({ line, column })
if (pos.line != null && pos.column != null)
resolve(pos as Position)
else
resolve(null)
})
}
const stackFnCallRE = /at (.*) \((.+):(\d+):(\d+)\)$/
const stackBarePathRE = /at ?(.*) (.+):(\d+):(\d+)$/
export async function interpretSourcePos(stackFrames: ParsedStack[], ctx: Vitest): Promise<ParsedStack[]> {
for (const frame of stackFrames) {
if ('sourcePos' in frame)
continue
const transformResult = ctx.server.moduleGraph.getModuleById(frame.file)?.ssrTransformResult
if (!transformResult)
continue
const sourcePos = await getOriginalPos(transformResult.map as any as RawSourceMap | undefined, frame)
if (sourcePos)
frame.sourcePos = sourcePos
}
return stackFrames
}
const stackIgnorePatterns = [
'node:internal',
'/vitest/dist/',
'/node_modules/chai/',
'/node_modules/tinypool/',
'/node_modules/tinyspy/',
]
export function parseStacktrace(e: ErrorWithDiff, full = false): ParsedStack[] {
if (e.stacks)
return e.stacks
const stackStr = e.stack || e.stackStr || ''
const stackFrames = stackStr
.split('\n')
.map((raw): ParsedStack | null => {
const line = raw.trim()
const match = line.match(stackFnCallRE) || line.match(stackBarePathRE)
if (!match)
return null
let file = slash(match[2])
if (file.startsWith('file://'))
file = file.slice(7)
if (!full && stackIgnorePatterns.some(p => file.includes(p)))
return null
return {
method: match[1],
file: match[2],
line: parseInt(match[3]),
column: parseInt(match[4]),
}
})
.filter(notNullish)
e.stacks = stackFrames
return stackFrames
}
export function posToNumber(
source: string,
pos: number | Position,
): number {
if (typeof pos === 'number')
return pos
const lines = source.split(lineSplitRE)
const { line, column } = pos
let start = 0
if (line > lines.length)
return source.length
for (let i = 0; i < line - 1; i++)
start += lines[i].length + 1
return start + column
}
export function numberToPos(
source: string,
offset: number | Position,
): Position {
if (typeof offset !== 'number')
return offset
if (offset > source.length) {
throw new Error(
`offset is longer than source length! offset ${offset} > length ${source.length}`,
)
}
const lines = source.split(lineSplitRE)
let counted = 0
let line = 0
let column = 0
for (; line < lines.length; line++) {
const lineLength = lines[line].length + 1
if (counted + lineLength >= offset) {
column = offset - counted + 1
break
}
counted += lineLength
}
return { line: line + 1, column }
}