forked from vitest-dev/vitest
/
inlineSnapshot.ts
140 lines (116 loc) · 4.46 KB
/
inlineSnapshot.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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import { promises as fs } from 'fs'
import type MagicString from 'magic-string'
import { rpc } from '../../../runtime/rpc'
import { getOriginalPos, lineSplitRE, numberToPos, posToNumber } from '../../../utils/source-map'
import { getCallLastIndex } from '../../../utils'
export interface InlineSnapshot {
snapshot: string
file: string
line: number
column: number
}
export async function saveInlineSnapshots(
snapshots: Array<InlineSnapshot>,
) {
const MagicString = (await import('magic-string')).default
const files = new Set(snapshots.map(i => i.file))
await Promise.all(Array.from(files).map(async (file) => {
const map = await rpc().getSourceMap(file)
const snaps = snapshots.filter(i => i.file === file)
const code = await fs.readFile(file, 'utf8')
const s = new MagicString(code)
for (const snap of snaps) {
const pos = await getOriginalPos(map, snap)
const index = posToNumber(code, pos!)
replaceInlineSnap(code, s, index, snap.snapshot)
}
const transformed = s.toString()
if (transformed !== code)
await fs.writeFile(file, transformed, 'utf-8')
}))
}
const startObjectRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*({)/m
function replaceObjectSnap(code: string, s: MagicString, index: number, newSnap: string) {
code = code.slice(index)
const startMatch = startObjectRegex.exec(code)
if (!startMatch)
return false
code = code.slice(startMatch.index)
const charIndex = getCallLastIndex(code)
if (charIndex === null)
return false
s.appendLeft(index + startMatch.index + charIndex, `, ${prepareSnapString(newSnap, code, index)}`)
return true
}
function prepareSnapString(snap: string, source: string, index: number) {
const lineIndex = numberToPos(source, index).line
const line = source.split(lineSplitRE)[lineIndex - 1]
const indent = line.match(/^\s*/)![0] || ''
const indentNext = indent.includes('\t') ? `${indent}\t` : `${indent} `
const lines = snap
.trim()
.replace(/\\/g, '\\\\')
.split(/\n/g)
const isOneline = lines.length <= 1
const quote = isOneline ? '\'' : '`'
if (isOneline)
return `'${lines.join('\n').replace(/'/g, '\\\'')}'`
else
return `${quote}\n${lines.map(i => i ? indentNext + i : '').join('\n').replace(/`/g, '\\`').replace(/\${/g, '\\${')}\n${indent}${quote}`
}
const startRegex = /(?:toMatchInlineSnapshot|toThrowErrorMatchingInlineSnapshot)\s*\(\s*[\w_$]*(['"`\)])/m
export function replaceInlineSnap(code: string, s: MagicString, index: number, newSnap: string) {
const startMatch = startRegex.exec(code.slice(index))
if (!startMatch)
return replaceObjectSnap(code, s, index, newSnap)
const quote = startMatch[1]
const startIndex = index + startMatch.index! + startMatch[0].length
const snapString = prepareSnapString(newSnap, code, index)
if (quote === ')') {
s.appendRight(startIndex - 1, snapString)
return true
}
const quoteEndRE = new RegExp(`(?:^|[^\\\\])${quote}`)
const endMatch = quoteEndRE.exec(code.slice(startIndex))
if (!endMatch)
return false
const endIndex = startIndex + endMatch.index! + endMatch[0].length
s.overwrite(startIndex - 1, endIndex, snapString)
return true
}
const INDENTATION_REGEX = /^([^\S\n]*)\S/m
export function stripSnapshotIndentation(inlineSnapshot: string) {
// Find indentation if exists.
const match = inlineSnapshot.match(INDENTATION_REGEX)
if (!match || !match[1]) {
// No indentation.
return inlineSnapshot
}
const indentation = match[1]
const lines = inlineSnapshot.split(/\n/g)
if (lines.length <= 2) {
// Must be at least 3 lines.
return inlineSnapshot
}
if (lines[0].trim() !== '' || lines[lines.length - 1].trim() !== '') {
// If not blank first and last lines, abort.
return inlineSnapshot
}
for (let i = 1; i < lines.length - 1; i++) {
if (lines[i] !== '') {
if (lines[i].indexOf(indentation) !== 0) {
// All lines except first and last should either be blank or have the same
// indent as the first line (or more). If this isn't the case we don't
// want to touch the snapshot at all.
return inlineSnapshot
}
lines[i] = lines[i].substring(indentation.length)
}
}
// Last line is a special case because it won't have the same indent as others
// but may still have been given some indent to line up.
lines[lines.length - 1] = ''
// Return inline snapshot, now at indent 0.
inlineSnapshot = lines.join('\n')
return inlineSnapshot
}