-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
/
InlineSnapshots.ts
171 lines (152 loc) · 5.03 KB
/
InlineSnapshots.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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import * as path from 'path';
import {types} from 'util';
import * as fs from 'graceful-fs';
import type {
CustomParser as PrettierCustomParser,
BuiltInParserName as PrettierParserName,
} from 'prettier-v2';
import semver = require('semver');
import {createSyncFn} from 'synckit';
import type {InlineSnapshot} from './types';
import {
groupSnapshotsByFile,
processInlineSnapshotsWithBabel,
processPrettierAst,
} from './utils';
type Prettier = typeof import('prettier-v2');
type WorkerFn = (
prettierPath: string,
filepath: string,
sourceFileWithSnapshots: string,
snapshotMatcherNames: Array<string>,
) => string;
const cachedPrettier = new Map<string, Prettier | WorkerFn>();
export function saveInlineSnapshots(
snapshots: Array<InlineSnapshot>,
rootDir: string,
prettierPath: string | null,
): void {
let prettier: Prettier | undefined = prettierPath
? (cachedPrettier.get(`module|${prettierPath}`) as Prettier)
: undefined;
let workerFn: WorkerFn | undefined = prettierPath
? (cachedPrettier.get(`worker|${prettierPath}`) as WorkerFn)
: undefined;
if (prettierPath && !prettier) {
try {
prettier =
// @ts-expect-error requireOutside
requireOutside(prettierPath) as Prettier;
cachedPrettier.set(`module|${prettierPath}`, prettier);
if (semver.gte(prettier.version, '3.0.0')) {
workerFn = createSyncFn(
require.resolve(/*webpackIgnore: true*/ './worker'),
) as WorkerFn;
cachedPrettier.set(`worker|${prettierPath}`, workerFn);
}
} catch (error) {
if (!types.isNativeError(error)) {
throw error;
}
if ((error as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') {
throw error;
}
}
}
const snapshotsByFile = groupSnapshotsByFile(snapshots);
for (const sourceFilePath of Object.keys(snapshotsByFile)) {
const {sourceFileWithSnapshots, snapshotMatcherNames, sourceFile} =
processInlineSnapshotsWithBabel(
snapshotsByFile[sourceFilePath],
sourceFilePath,
rootDir,
);
let newSourceFile = sourceFileWithSnapshots;
if (workerFn) {
newSourceFile = workerFn(
prettierPath!,
sourceFilePath,
sourceFileWithSnapshots,
snapshotMatcherNames,
);
} else if (prettier && semver.gte(prettier.version, '1.5.0')) {
newSourceFile = runPrettier(
prettier,
sourceFilePath,
sourceFileWithSnapshots,
snapshotMatcherNames,
);
}
if (newSourceFile !== sourceFile) {
fs.writeFileSync(sourceFilePath, newSourceFile);
}
}
}
const runPrettier = (
prettier: Prettier,
sourceFilePath: string,
sourceFileWithSnapshots: string,
snapshotMatcherNames: Array<string>,
) => {
// Resolve project configuration.
// For older versions of Prettier, do not load configuration.
const config = prettier.resolveConfig
? prettier.resolveConfig.sync(sourceFilePath, {editorconfig: true})
: null;
// Prioritize parser found in the project config.
// If not found detect the parser for the test file.
// For older versions of Prettier, fallback to a simple parser detection.
// @ts-expect-error - `inferredParser` is `string`
const inferredParser: PrettierParserName | null | undefined =
(typeof config?.parser === 'string' && config.parser) ||
(prettier.getFileInfo
? prettier.getFileInfo.sync(sourceFilePath).inferredParser
: simpleDetectParser(sourceFilePath));
if (!inferredParser) {
throw new Error(
`Could not infer Prettier parser for file ${sourceFilePath}`,
);
}
// Snapshots have now been inserted. Run prettier to make sure that the code is
// formatted, except snapshot indentation. Snapshots cannot be formatted until
// after the initial format because we don't know where the call expression
// will be placed (specifically its indentation), so we have to do two
// prettier.format calls back-to-back.
return prettier.format(
prettier.format(sourceFileWithSnapshots, {
...config,
filepath: sourceFilePath,
}),
{
...config,
filepath: sourceFilePath,
parser: createFormattingParser(snapshotMatcherNames, inferredParser),
},
);
};
// This parser formats snapshots to the correct indentation.
const createFormattingParser =
(
snapshotMatcherNames: Array<string>,
inferredParser: PrettierParserName,
): PrettierCustomParser =>
(text, parsers, options) => {
// Workaround for https://github.com/prettier/prettier/issues/3150
options.parser = inferredParser;
const ast = parsers[inferredParser](text, options);
processPrettierAst(ast, options, snapshotMatcherNames);
return ast;
};
const simpleDetectParser = (filePath: string): PrettierParserName => {
const extname = path.extname(filePath);
if (/\.tsx?$/.test(extname)) {
return 'typescript';
}
return 'babel';
};