mirror of
https://github.com/vitest-dev/vitest.git
synced 2026-01-18 16:31:32 +00:00
187 lines
5.7 KiB
TypeScript
187 lines
5.7 KiB
TypeScript
import { resolve } from 'pathe'
|
|
import type { SourceMapInput } from '@jridgewell/trace-mapping'
|
|
import { TraceMap, originalPositionFor } from '@jridgewell/trace-mapping'
|
|
import type { ErrorWithDiff, ParsedStack } from './types'
|
|
import { isPrimitive, notNullish } from './helpers'
|
|
|
|
export { TraceMap, originalPositionFor, generatedPositionFor } from '@jridgewell/trace-mapping'
|
|
export type { SourceMapInput } from '@jridgewell/trace-mapping'
|
|
|
|
export interface StackTraceParserOptions {
|
|
ignoreStackEntries?: (RegExp | string)[]
|
|
getSourceMap?: (file: string) => unknown
|
|
}
|
|
|
|
const CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m
|
|
const SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/
|
|
|
|
const stackIgnorePatterns = [
|
|
'node:internal',
|
|
/\/packages\/\w+\/dist\//,
|
|
/\/@vitest\/\w+\/dist\//,
|
|
'/vitest/dist/',
|
|
'/vitest/src/',
|
|
'/vite-node/dist/',
|
|
'/vite-node/src/',
|
|
'/node_modules/chai/',
|
|
'/node_modules/tinypool/',
|
|
'/node_modules/tinyspy/',
|
|
'/deps/chai.js',
|
|
/__vitest_browser__/,
|
|
]
|
|
|
|
function extractLocation(urlLike: string) {
|
|
// Fail-fast but return locations like "(native)"
|
|
if (!urlLike.includes(':'))
|
|
return [urlLike]
|
|
|
|
const regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/
|
|
const parts = regExp.exec(urlLike.replace(/^\(|\)$/g, ''))
|
|
if (!parts)
|
|
return [urlLike]
|
|
let url = parts[1]
|
|
if (url.startsWith('http:') || url.startsWith('https:')) {
|
|
const urlObj = new URL(url)
|
|
url = urlObj.pathname
|
|
}
|
|
if (url.startsWith('/@fs/')) {
|
|
url
|
|
= url.slice(typeof process !== 'undefined' && process.platform === 'win32' ? 5 : 4)
|
|
}
|
|
return [url, parts[2] || undefined, parts[3] || undefined]
|
|
}
|
|
|
|
export function parseSingleFFOrSafariStack(raw: string): ParsedStack | null {
|
|
let line = raw.trim()
|
|
|
|
if (SAFARI_NATIVE_CODE_REGEXP.test(line))
|
|
return null
|
|
|
|
if (line.includes(' > eval'))
|
|
line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ':$1')
|
|
|
|
if (!line.includes('@') && !line.includes(':'))
|
|
return null
|
|
|
|
const functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/
|
|
const matches = line.match(functionNameRegex)
|
|
const functionName = matches && matches[1] ? matches[1] : undefined
|
|
const [url, lineNumber, columnNumber] = extractLocation(line.replace(functionNameRegex, ''))
|
|
|
|
if (!url || !lineNumber || !columnNumber)
|
|
return null
|
|
|
|
return {
|
|
file: url,
|
|
method: functionName || '',
|
|
line: Number.parseInt(lineNumber),
|
|
column: Number.parseInt(columnNumber),
|
|
}
|
|
}
|
|
|
|
export function parseSingleStack(raw: string) {
|
|
const line = raw.trim()
|
|
if (!CHROME_IE_STACK_REGEXP.test(line))
|
|
return parseSingleFFOrSafariStack(line)
|
|
return parseSingleV8Stack(line)
|
|
}
|
|
|
|
// Based on https://github.com/stacktracejs/error-stack-parser
|
|
// Credit to stacktracejs
|
|
export function parseSingleV8Stack(raw: string): ParsedStack | null {
|
|
let line = raw.trim()
|
|
|
|
if (!CHROME_IE_STACK_REGEXP.test(line))
|
|
return null
|
|
|
|
if (line.includes('(eval '))
|
|
line = line.replace(/eval code/g, 'eval').replace(/(\(eval at [^()]*)|(,.*$)/g, '')
|
|
|
|
let sanitizedLine = line
|
|
.replace(/^\s+/, '')
|
|
.replace(/\(eval code/g, '(')
|
|
.replace(/^.*?\s+/, '')
|
|
|
|
// capture and preserve the parenthesized location "(/foo/my bar.js:12:87)" in
|
|
// case it has spaces in it, as the string is split on \s+ later on
|
|
const location = sanitizedLine.match(/ (\(.+\)$)/)
|
|
|
|
// remove the parenthesized location from the line, if it was matched
|
|
sanitizedLine = location ? sanitizedLine.replace(location[0], '') : sanitizedLine
|
|
|
|
// if a location was matched, pass it to extractLocation() otherwise pass all sanitizedLine
|
|
// because this line doesn't have function name
|
|
const [url, lineNumber, columnNumber] = extractLocation(location ? location[1] : sanitizedLine)
|
|
let method = (location && sanitizedLine) || ''
|
|
let file = (url && ['eval', '<anonymous>'].includes(url)) ? undefined : url
|
|
|
|
if (!file || !lineNumber || !columnNumber)
|
|
return null
|
|
|
|
if (method.startsWith('async '))
|
|
method = method.slice(6)
|
|
|
|
if (file.startsWith('file://'))
|
|
file = file.slice(7)
|
|
|
|
// normalize Windows path (\ -> /)
|
|
file = resolve(file)
|
|
|
|
if (method)
|
|
method = method.replace(/__vite_ssr_import_\d+__\./g, '')
|
|
|
|
return {
|
|
method,
|
|
file,
|
|
line: Number.parseInt(lineNumber),
|
|
column: Number.parseInt(columnNumber),
|
|
}
|
|
}
|
|
|
|
export function parseStacktrace(stack: string, options: StackTraceParserOptions = {}): ParsedStack[] {
|
|
const { ignoreStackEntries = stackIgnorePatterns } = options
|
|
let stacks = !CHROME_IE_STACK_REGEXP.test(stack)
|
|
? parseFFOrSafariStackTrace(stack)
|
|
: parseV8Stacktrace(stack)
|
|
if (ignoreStackEntries.length)
|
|
stacks = stacks.filter(stack => !ignoreStackEntries.some(p => stack.file.match(p)))
|
|
return stacks.map((stack) => {
|
|
const map = options.getSourceMap?.(stack.file) as SourceMapInput | null | undefined
|
|
if (!map || typeof map !== 'object' || !map.version)
|
|
return stack
|
|
const traceMap = new TraceMap(map)
|
|
const { line, column } = originalPositionFor(traceMap, stack)
|
|
if (line != null && column != null)
|
|
return { ...stack, line, column }
|
|
return stack
|
|
})
|
|
}
|
|
|
|
function parseFFOrSafariStackTrace(stack: string): ParsedStack[] {
|
|
return stack
|
|
.split('\n')
|
|
.map(line => parseSingleFFOrSafariStack(line))
|
|
.filter(notNullish)
|
|
}
|
|
|
|
function parseV8Stacktrace(stack: string): ParsedStack[] {
|
|
return stack
|
|
.split('\n')
|
|
.map(line => parseSingleV8Stack(line))
|
|
.filter(notNullish)
|
|
}
|
|
|
|
export function parseErrorStacktrace(e: ErrorWithDiff, options: StackTraceParserOptions = {}): ParsedStack[] {
|
|
if (!e || isPrimitive(e))
|
|
return []
|
|
|
|
if (e.stacks)
|
|
return e.stacks
|
|
|
|
const stackStr = e.stack || e.stackStr || ''
|
|
const stackFrames = parseStacktrace(stackStr, options)
|
|
|
|
e.stacks = stackFrames
|
|
return stackFrames
|
|
}
|