diff --git a/CHANGELOG.md b/CHANGELOG.md
index 87390ddbfaa8..88e314d92508 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
- `[@jest/fake-timers]` [**BREAKING**] Upgrade `@sinonjs/fake-timers` to v11 ([#14544](https://github.com/jestjs/jest/pull/14544))
- `[@jest/schemas]` Upgrade `@sinclair/typebox` to v0.31 ([#14072](https://github.com/jestjs/jest/pull/14072))
- `[jest-snapshot]` [**BREAKING**] Add support for [Error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) in snapshots ([#13965](https://github.com/facebook/jest/pull/13965))
+- `[jest-snapshot]` Support Prettier 3 ([#14566](https://github.com/facebook/jest/pull/14566))
- `[pretty-format]` [**BREAKING**] Do not render empty string children (`''`) in React plugin ([#14470](https://github.com/facebook/jest/pull/14470))
### Fixes
diff --git a/constraints.pro b/constraints.pro
index 62235e22de6b..d40b057ca82b 100644
--- a/constraints.pro
+++ b/constraints.pro
@@ -22,7 +22,9 @@ gen_enforced_dependency(WorkspaceCwd, DependencyIdent, DependencyRange2, Depende
% @types/node in the root need to stay on ~14.14.45
'@types/node',
% upgrading the entire repository is a breaking change
- 'glob'
+ 'glob',
+ % repository and snapshot
+ 'prettier'
]).
% Enforces that a dependency doesn't appear in both `dependencies` and `devDependencies`
diff --git a/docs/Configuration.md b/docs/Configuration.md
index 813dd8c33c45..8dc961201f12 100644
--- a/docs/Configuration.md
+++ b/docs/Configuration.md
@@ -1145,42 +1145,6 @@ Default: `'prettier'`
Sets the path to the [`prettier`](https://prettier.io/) node module used to update inline snapshots.
-
-Prettier version 3 is not supported!
-
-You can either pass `prettierPath: null` in your config to disable using prettier if you don't need it, or use v2 of Prettier solely for Jest.
-
-```json title="package.json"
-{
- "devDependencies": {
- "prettier-2": "npm:prettier@^2"
- }
-}
-```
-
-```js tab
-/** @type {import('jest').Config} */
-const config = {
- prettierPath: require.resolve('prettier-2'),
-};
-
-module.exports = config;
-```
-
-```ts tab
-import type {Config} from 'jest';
-
-const config: Config = {
- prettierPath: require.resolve('prettier-2'),
-};
-
-export default config;
-```
-
-We hope to support Prettier v3 seamlessly out of the box in a future version of Jest. See [this](https://github.com/jestjs/jest/issues/14305) tracking issue.
-
-
-
### `projects` \[array<string | ProjectConfig>]
Default: `undefined`
diff --git a/e2e/__tests__/toMatchInlineSnapshotWithPretttier3.test.ts b/e2e/__tests__/toMatchInlineSnapshotWithPretttier3.test.ts
index 2cc01798b60e..ef05f993528a 100644
--- a/e2e/__tests__/toMatchInlineSnapshotWithPretttier3.test.ts
+++ b/e2e/__tests__/toMatchInlineSnapshotWithPretttier3.test.ts
@@ -28,10 +28,10 @@ afterAll(() => {
cleanup(JEST_CONFIG_PATH);
});
-test('throws correct error', () => {
+test('supports passing `null` as `prettierPath`', () => {
writeFiles(DIR, {
'jest.config.js': `
- module.exports = {prettierPath: require.resolve('prettier')};
+ module.exports = {prettierPath: null};
`,
});
writeFiles(TESTS_DIR, {
@@ -42,16 +42,14 @@ test('throws correct error', () => {
`,
});
const {stderr, exitCode} = runJest(DIR, ['--ci=false']);
- expect(stderr).toContain(
- 'Jest: Inline Snapshots are not supported when using Prettier 3.0.0 or above.',
- );
- expect(exitCode).toBe(1);
+ expect(stderr).toContain('Snapshots: 1 written, 1 total');
+ expect(exitCode).toBe(0);
});
-test('supports passing `null` as `prettierPath`', () => {
+test('supports passing `prettier-2` as `prettierPath`', () => {
writeFiles(DIR, {
'jest.config.js': `
- module.exports = {prettierPath: null};
+ module.exports = {prettierPath: require.resolve('prettier-2')};
`,
});
writeFiles(TESTS_DIR, {
@@ -66,10 +64,10 @@ test('supports passing `null` as `prettierPath`', () => {
expect(exitCode).toBe(0);
});
-test('supports passing `prettier-2` as `prettierPath`', () => {
+test('supports passing `prettier` as `prettierPath`', () => {
writeFiles(DIR, {
'jest.config.js': `
- module.exports = {prettierPath: require.resolve('prettier-2')};
+ module.exports = {prettierPath: require.resolve('prettier')};
`,
});
writeFiles(TESTS_DIR, {
diff --git a/packages/jest-snapshot/package.json b/packages/jest-snapshot/package.json
index 8987424c1284..26593541288a 100644
--- a/packages/jest-snapshot/package.json
+++ b/packages/jest-snapshot/package.json
@@ -36,7 +36,8 @@
"jest-util": "workspace:*",
"natural-compare": "^1.4.0",
"pretty-format": "workspace:*",
- "semver": "^7.5.3"
+ "semver": "^7.5.3",
+ "synckit": "^0.8.5"
},
"devDependencies": {
"@babel/preset-flow": "^7.7.2",
@@ -46,11 +47,12 @@
"@types/babel__core": "^7.1.14",
"@types/graceful-fs": "^4.1.3",
"@types/natural-compare": "^1.4.0",
- "@types/prettier": "^2.1.5",
+ "@types/prettier-v2": "npm:@types/prettier@^2.1.5",
"@types/semver": "^7.1.0",
"ansi-regex": "^5.0.1",
"ansi-styles": "^5.0.0",
- "prettier": "^2.1.1",
+ "prettier": "^3.0.3",
+ "prettier-v2": "npm:prettier@^2.1.5",
"tsd-lite": "^0.8.0"
},
"engines": {
diff --git a/packages/jest-snapshot/src/InlineSnapshots.ts b/packages/jest-snapshot/src/InlineSnapshots.ts
index ebdf2724fcce..8a1b86cae44b 100644
--- a/packages/jest-snapshot/src/InlineSnapshots.ts
+++ b/packages/jest-snapshot/src/InlineSnapshots.ts
@@ -7,65 +7,53 @@
import * as path from 'path';
import {types} from 'util';
-import type {ParseResult, PluginItem} from '@babel/core';
-import type {
- Expression,
- File,
- Node,
- Program,
- TraversalAncestors,
-} from '@babel/types';
import * as fs from 'graceful-fs';
import type {
CustomParser as PrettierCustomParser,
BuiltInParserName as PrettierParserName,
-} from 'prettier';
+} from 'prettier-v2';
import semver = require('semver');
-import type {Frame} from 'jest-message-util';
-import {escapeBacktickString} from './utils';
-
-// prettier-ignore
-const generate = (
- // @ts-expect-error requireOutside Babel transform
- requireOutside('@babel/generator') as typeof import('@babel/generator')
-).default;
-const {
- isAwaitExpression,
- templateElement,
- templateLiteral,
- traverse,
- traverseFast,
-} =
- // @ts-expect-error requireOutside Babel transform
- requireOutside('@babel/types') as typeof import('@babel/types');
-// @ts-expect-error requireOutside Babel transform
-const {parseSync} = requireOutside(
- '@babel/core',
-) as typeof import('@babel/core');
-
-type Prettier = typeof import('prettier');
+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;
-export type InlineSnapshot = {
- snapshot: string;
- frame: Frame;
- node?: Expression;
-};
+const cachedPrettier = new Map();
export function saveInlineSnapshots(
snapshots: Array,
rootDir: string,
prettierPath: string | null,
): void {
- let prettier: Prettier | null = null;
- if (prettierPath) {
+ 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 {
- // @ts-expect-error requireOutside Babel transform
- prettier = requireOutside(prettierPath) as Prettier;
+ prettier =
+ // @ts-expect-error requireOutside
+ requireOutside(prettierPath) as Prettier;
+ cachedPrettier.set(`module|${prettierPath}`, prettier);
if (semver.gte(prettier.version, '3.0.0')) {
- throw new Error(
- 'Jest: Inline Snapshots are not supported when using Prettier 3.0.0 or above.\nSee https://jestjs.io/docs/configuration/#prettierpath-string for alternatives.',
+ workerFn = createSyncFn(
+ require.resolve(/*webpackIgnore: true*/ './worker'),
);
+ cachedPrettier.set(`worker|${prettierPath}`, workerFn);
}
} catch (error) {
if (!types.isNativeError(error)) {
@@ -81,220 +69,36 @@ export function saveInlineSnapshots(
const snapshotsByFile = groupSnapshotsByFile(snapshots);
for (const sourceFilePath of Object.keys(snapshotsByFile)) {
- saveSnapshotsForFile(
- snapshotsByFile[sourceFilePath],
- sourceFilePath,
- rootDir,
- prettier && semver.gte(prettier.version, '1.5.0') ? prettier : undefined,
- );
- }
-}
-
-const saveSnapshotsForFile = (
- snapshots: Array,
- sourceFilePath: string,
- rootDir: string,
- prettier: Prettier | undefined,
-) => {
- const sourceFile = fs.readFileSync(sourceFilePath, 'utf8');
-
- // TypeScript projects may not have a babel config; make sure they can be parsed anyway.
- const presets = [require.resolve('babel-preset-current-node-syntax')];
- const plugins: Array = [];
- if (/\.([cm]?ts|tsx)$/.test(sourceFilePath)) {
- plugins.push([
- require.resolve('@babel/plugin-syntax-typescript'),
- {isTSX: sourceFilePath.endsWith('x')},
- // unique name to make sure Babel does not complain about a possible duplicate plugin.
- 'TypeScript syntax plugin added by Jest snapshot',
- ]);
- }
-
- // Record the matcher names seen during traversal and pass them down one
- // by one to formatting parser.
- const snapshotMatcherNames: Array = [];
-
- let ast: ParseResult | null = null;
-
- try {
- ast = parseSync(sourceFile, {
- filename: sourceFilePath,
- plugins,
- presets,
- root: rootDir,
- });
- } catch (error: any) {
- // attempt to recover from missing jsx plugin
- if (error.message.includes('@babel/plugin-syntax-jsx')) {
- try {
- const jsxSyntaxPlugin: PluginItem = [
- require.resolve('@babel/plugin-syntax-jsx'),
- {},
- // unique name to make sure Babel does not complain about a possible duplicate plugin.
- 'JSX syntax plugin added by Jest snapshot',
- ];
- ast = parseSync(sourceFile, {
- filename: sourceFilePath,
- plugins: [...plugins, jsxSyntaxPlugin],
- presets,
- root: rootDir,
- });
- } catch {
- throw error;
- }
- } else {
- throw error;
- }
- }
-
- if (!ast) {
- throw new Error(`jest-snapshot: Failed to parse ${sourceFilePath}`);
- }
- traverseAst(snapshots, ast, snapshotMatcherNames);
-
- // substitute in the snapshots in reverse order, so slice calculations aren't thrown off.
- const sourceFileWithSnapshots = snapshots.reduceRight(
- (sourceSoFar, nextSnapshot) => {
- const {node} = nextSnapshot;
- if (
- !node ||
- typeof node.start !== 'number' ||
- typeof node.end !== 'number'
- ) {
- throw new Error('Jest: no snapshot insert location found');
- }
+ const {sourceFileWithSnapshots, snapshotMatcherNames, sourceFile} =
+ processInlineSnapshotsWithBabel(
+ snapshotsByFile[sourceFilePath],
+ sourceFilePath,
+ rootDir,
+ );
- // A hack to prevent unexpected line breaks in the generated code
- node.loc!.end.line = node.loc!.start.line;
+ let newSourceFile = sourceFileWithSnapshots;
- return (
- sourceSoFar.slice(0, node.start) +
- generate(node, {retainLines: true}).code.trim() +
- sourceSoFar.slice(node.end)
+ if (workerFn) {
+ newSourceFile = workerFn(
+ prettierPath!,
+ sourceFilePath,
+ sourceFileWithSnapshots,
+ snapshotMatcherNames,
);
- },
- sourceFile,
- );
-
- const newSourceFile = prettier
- ? runPrettier(
+ } else if (prettier && semver.gte(prettier.version, '1.5.0')) {
+ newSourceFile = runPrettier(
prettier,
sourceFilePath,
sourceFileWithSnapshots,
snapshotMatcherNames,
- )
- : sourceFileWithSnapshots;
-
- if (newSourceFile !== sourceFile) {
- fs.writeFileSync(sourceFilePath, newSourceFile);
- }
-};
-
-const groupSnapshotsBy =
- (createKey: (inlineSnapshot: InlineSnapshot) => string) =>
- (snapshots: Array) =>
- snapshots.reduce>>(
- (object, inlineSnapshot) => {
- const key = createKey(inlineSnapshot);
- return {...object, [key]: (object[key] || []).concat(inlineSnapshot)};
- },
- {},
- );
-
-const groupSnapshotsByFrame = groupSnapshotsBy(({frame: {line, column}}) =>
- typeof line === 'number' && typeof column === 'number'
- ? `${line}:${column - 1}`
- : '',
-);
-const groupSnapshotsByFile = groupSnapshotsBy(({frame: {file}}) => file);
-
-const indent = (snapshot: string, numIndents: number, indentation: string) => {
- const lines = snapshot.split('\n');
- // Prevent re-indentation of inline snapshots.
- if (
- lines.length >= 2 &&
- lines[1].startsWith(indentation.repeat(numIndents + 1))
- ) {
- return snapshot;
- }
-
- return lines
- .map((line, index) => {
- if (index === 0) {
- // First line is either a 1-line snapshot or a blank line.
- return line;
- } else if (index === lines.length - 1) {
- // The last line should be placed on the same level as the expect call.
- return indentation.repeat(numIndents) + line;
- } else {
- // Do not indent empty lines.
- if (line === '') {
- return line;
- }
-
- // Not last line, indent one level deeper than expect call.
- return indentation.repeat(numIndents + 1) + line;
- }
- })
- .join('\n');
-};
-
-const traverseAst = (
- snapshots: Array,
- ast: File | Program,
- snapshotMatcherNames: Array,
-) => {
- const groupedSnapshots = groupSnapshotsByFrame(snapshots);
- const remainingSnapshots = new Set(snapshots.map(({snapshot}) => snapshot));
-
- traverseFast(ast, (node: Node) => {
- if (node.type !== 'CallExpression') return;
-
- const {arguments: args, callee} = node;
- if (
- callee.type !== 'MemberExpression' ||
- callee.property.type !== 'Identifier' ||
- callee.property.loc == null
- ) {
- return;
- }
- const {line, column} = callee.property.loc.start;
- const snapshotsForFrame = groupedSnapshots[`${line}:${column}`];
- if (!snapshotsForFrame) {
- return;
- }
- if (snapshotsForFrame.length > 1) {
- throw new Error(
- 'Jest: Multiple inline snapshots for the same call are not supported.',
);
}
- const inlineSnapshot = snapshotsForFrame[0];
- inlineSnapshot.node = node;
- snapshotMatcherNames.push(callee.property.name);
-
- const snapshotIndex = args.findIndex(
- ({type}) => type === 'TemplateLiteral' || type === 'StringLiteral',
- );
-
- const {snapshot} = inlineSnapshot;
- remainingSnapshots.delete(snapshot);
- const replacementNode = templateLiteral(
- [templateElement({raw: escapeBacktickString(snapshot)})],
- [],
- );
-
- if (snapshotIndex > -1) {
- args[snapshotIndex] = replacementNode;
- } else {
- args.push(replacementNode);
+ if (newSourceFile !== sourceFile) {
+ fs.writeFileSync(sourceFilePath, newSourceFile);
}
- });
-
- if (remainingSnapshots.size > 0) {
- throw new Error("Jest: Couldn't locate all inline snapshots.");
}
-};
+}
const runPrettier = (
prettier: Prettier,
@@ -313,7 +117,7 @@ const runPrettier = (
// For older versions of Prettier, fallback to a simple parser detection.
// @ts-expect-error - `inferredParser` is `string`
const inferredParser: PrettierParserName | null | undefined =
- (config && typeof config.parser === 'string' && config.parser) ||
+ (typeof config?.parser === 'string' && config.parser) ||
(prettier.getFileInfo
? prettier.getFileInfo.sync(sourceFilePath).inferredParser
: simpleDetectParser(sourceFilePath));
@@ -353,61 +157,7 @@ const createFormattingParser =
options.parser = inferredParser;
const ast = parsers[inferredParser](text, options);
- traverse(ast, (node: Node, ancestors: TraversalAncestors) => {
- if (node.type !== 'CallExpression') return;
-
- const {arguments: args, callee} = node;
- if (
- callee.type !== 'MemberExpression' ||
- callee.property.type !== 'Identifier' ||
- !snapshotMatcherNames.includes(callee.property.name) ||
- !callee.loc ||
- callee.computed
- ) {
- return;
- }
-
- let snapshotIndex: number | undefined;
- let snapshot: string | undefined;
- for (let i = 0; i < args.length; i++) {
- const node = args[i];
- if (node.type === 'TemplateLiteral') {
- snapshotIndex = i;
- snapshot = node.quasis[0].value.raw;
- }
- }
- if (snapshot === undefined) {
- return;
- }
-
- const parent = ancestors[ancestors.length - 1].node;
- const startColumn =
- isAwaitExpression(parent) && parent.loc
- ? parent.loc.start.column
- : callee.loc.start.column;
-
- const useSpaces = !options.useTabs;
- snapshot = indent(
- snapshot,
- Math.ceil(
- useSpaces
- ? startColumn / (options.tabWidth ?? 1)
- : // Each tab is 2 characters.
- startColumn / 2,
- ),
- useSpaces ? ' '.repeat(options.tabWidth ?? 1) : '\t',
- );
-
- const replacementNode = templateLiteral(
- [
- templateElement({
- raw: snapshot,
- }),
- ],
- [],
- );
- args[snapshotIndex!] = replacementNode;
- });
+ processPrettierAst(ast, options, snapshotMatcherNames);
return ast;
};
diff --git a/packages/jest-snapshot/src/State.ts b/packages/jest-snapshot/src/State.ts
index 16864e023dcd..e865abd89058 100644
--- a/packages/jest-snapshot/src/State.ts
+++ b/packages/jest-snapshot/src/State.ts
@@ -8,8 +8,8 @@
import * as fs from 'graceful-fs';
import type {Config} from '@jest/types';
import {getStackTraceLines, getTopFrame} from 'jest-message-util';
-import {InlineSnapshot, saveInlineSnapshots} from './InlineSnapshots';
-import type {SnapshotData, SnapshotFormat} from './types';
+import {saveInlineSnapshots} from './InlineSnapshots';
+import type {InlineSnapshot, SnapshotData, SnapshotFormat} from './types';
import {
addExtraLineBreaks,
getSnapshotData,
diff --git a/packages/jest-snapshot/src/__tests__/InlineSnapshots.test.ts b/packages/jest-snapshot/src/__tests__/InlineSnapshots.test.ts
index 67541646894d..47e2153cd919 100644
--- a/packages/jest-snapshot/src/__tests__/InlineSnapshots.test.ts
+++ b/packages/jest-snapshot/src/__tests__/InlineSnapshots.test.ts
@@ -8,23 +8,16 @@
import {tmpdir} from 'os';
import * as path from 'path';
import * as fs from 'graceful-fs';
-import prettier = require('prettier');
import type {Frame} from 'jest-message-util';
import {saveInlineSnapshots} from '../InlineSnapshots';
+const prettier = require('prettier') as typeof import('prettier-v2');
+
jest.mock('prettier', () => {
const realPrettier =
- jest.requireActual('prettier');
+ jest.requireActual('prettier-v2');
const mockPrettier = {
- format: (text, opts) =>
- realPrettier.format(text, {
- pluginSearchDirs: [
- (require('path') as typeof import('path')).dirname(
- require.resolve('prettier'),
- ),
- ],
- ...opts,
- }),
+ format: realPrettier.format,
getFileInfo: {
sync: () => ({ignored: false, inferredParser: 'babel'}),
} as unknown as typeof prettier.getFileInfo,
diff --git a/packages/jest-snapshot/src/types.ts b/packages/jest-snapshot/src/types.ts
index 541c191193a7..ed052f4491ce 100644
--- a/packages/jest-snapshot/src/types.ts
+++ b/packages/jest-snapshot/src/types.ts
@@ -5,7 +5,9 @@
* LICENSE file in the root directory of this source tree.
*/
+import type {Expression} from '@babel/types';
import type {MatcherContext} from 'expect';
+import type {Frame} from 'jest-message-util';
import type {PrettyFormatOptions} from 'pretty-format';
import type SnapshotState from './State';
@@ -74,3 +76,9 @@ export interface SnapshotMatchers, T> {
}
export type SnapshotFormat = Omit;
+
+export type InlineSnapshot = {
+ snapshot: string;
+ frame: Frame;
+ node?: Expression;
+};
diff --git a/packages/jest-snapshot/src/utils.ts b/packages/jest-snapshot/src/utils.ts
index 74c499c2a2a2..4d0ece527a74 100644
--- a/packages/jest-snapshot/src/utils.ts
+++ b/packages/jest-snapshot/src/utils.ts
@@ -6,6 +6,14 @@
*/
import * as path from 'path';
+import type {ParseResult, PluginItem} from '@babel/core';
+import type {
+ File,
+ Node,
+ Program,
+ TemplateLiteral,
+ TraversalAncestors,
+} from '@babel/types';
import chalk = require('chalk');
import * as fs from 'graceful-fs';
import naturalCompare = require('natural-compare');
@@ -15,7 +23,7 @@ import {
format as prettyFormat,
} from 'pretty-format';
import {getSerializers} from './plugins';
-import type {SnapshotData} from './types';
+import type {InlineSnapshot, SnapshotData} from './types';
export const SNAPSHOT_VERSION = '1';
const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
@@ -270,3 +278,290 @@ export const deepMerge = (target: any, source: any): any => {
return target;
};
+
+const indent = (
+ snapshot: string,
+ numIndents: number,
+ indentation: string,
+): string => {
+ const lines = snapshot.split('\n');
+ // Prevent re-indentation of inline snapshots.
+ if (
+ lines.length >= 2 &&
+ lines[1].startsWith(indentation.repeat(numIndents + 1))
+ ) {
+ return snapshot;
+ }
+
+ return lines
+ .map((line, index) => {
+ if (index === 0) {
+ // First line is either a 1-line snapshot or a blank line.
+ return line;
+ } else if (index === lines.length - 1) {
+ // The last line should be placed on the same level as the expect call.
+ return indentation.repeat(numIndents) + line;
+ } else {
+ // Do not indent empty lines.
+ if (line === '') {
+ return line;
+ }
+
+ // Not last line, indent one level deeper than expect call.
+ return indentation.repeat(numIndents + 1) + line;
+ }
+ })
+ .join('\n');
+};
+
+const generate = // @ts-expect-error requireOutside Babel transform
+ (requireOutside('@babel/generator') as typeof import('@babel/generator'))
+ .default;
+
+// @ts-expect-error requireOutside Babel transform
+const {parseSync, types} = requireOutside(
+ '@babel/core',
+) as typeof import('@babel/core');
+const {
+ isAwaitExpression,
+ templateElement,
+ templateLiteral,
+ traverseFast,
+ traverse,
+} = types;
+
+export const processInlineSnapshotsWithBabel = (
+ snapshots: Array,
+ sourceFilePath: string,
+ rootDir: string,
+): {
+ snapshotMatcherNames: Array;
+ sourceFile: string;
+ sourceFileWithSnapshots: string;
+} => {
+ const sourceFile = fs.readFileSync(sourceFilePath, 'utf8');
+
+ // TypeScript projects may not have a babel config; make sure they can be parsed anyway.
+ const presets = [require.resolve('babel-preset-current-node-syntax')];
+ const plugins: Array = [];
+ if (/\.([cm]?ts|tsx)$/.test(sourceFilePath)) {
+ plugins.push([
+ require.resolve('@babel/plugin-syntax-typescript'),
+ {isTSX: sourceFilePath.endsWith('x')},
+ // unique name to make sure Babel does not complain about a possible duplicate plugin.
+ 'TypeScript syntax plugin added by Jest snapshot',
+ ]);
+ }
+
+ // Record the matcher names seen during traversal and pass them down one
+ // by one to formatting parser.
+ const snapshotMatcherNames: Array = [];
+
+ let ast: ParseResult | null = null;
+
+ try {
+ ast = parseSync(sourceFile, {
+ filename: sourceFilePath,
+ plugins,
+ presets,
+ root: rootDir,
+ });
+ } catch (error: any) {
+ // attempt to recover from missing jsx plugin
+ if (error.message.includes('@babel/plugin-syntax-jsx')) {
+ try {
+ const jsxSyntaxPlugin: PluginItem = [
+ require.resolve('@babel/plugin-syntax-jsx'),
+ {},
+ // unique name to make sure Babel does not complain about a possible duplicate plugin.
+ 'JSX syntax plugin added by Jest snapshot',
+ ];
+ ast = parseSync(sourceFile, {
+ filename: sourceFilePath,
+ plugins: [...plugins, jsxSyntaxPlugin],
+ presets,
+ root: rootDir,
+ });
+ } catch {
+ throw error;
+ }
+ } else {
+ throw error;
+ }
+ }
+
+ if (!ast) {
+ throw new Error(`jest-snapshot: Failed to parse ${sourceFilePath}`);
+ }
+ traverseAst(snapshots, ast, snapshotMatcherNames);
+
+ return {
+ snapshotMatcherNames,
+ sourceFile,
+ // substitute in the snapshots in reverse order, so slice calculations aren't thrown off.
+ sourceFileWithSnapshots: snapshots.reduceRight(
+ (sourceSoFar, nextSnapshot) => {
+ const {node} = nextSnapshot;
+ if (
+ !node ||
+ typeof node.start !== 'number' ||
+ typeof node.end !== 'number'
+ ) {
+ throw new Error('Jest: no snapshot insert location found');
+ }
+
+ // A hack to prevent unexpected line breaks in the generated code
+ node.loc!.end.line = node.loc!.start.line;
+
+ return (
+ sourceSoFar.slice(0, node.start) +
+ generate(node, {retainLines: true}).code.trim() +
+ sourceSoFar.slice(node.end)
+ );
+ },
+ sourceFile,
+ ),
+ };
+};
+
+export const processPrettierAst = (
+ ast: File,
+ options: Record | null,
+ snapshotMatcherNames: Array,
+ keepNode?: boolean,
+): void => {
+ traverse(ast, (node: Node, ancestors: TraversalAncestors) => {
+ if (node.type !== 'CallExpression') return;
+
+ const {arguments: args, callee} = node;
+ if (
+ callee.type !== 'MemberExpression' ||
+ callee.property.type !== 'Identifier' ||
+ !snapshotMatcherNames.includes(callee.property.name) ||
+ !callee.loc ||
+ callee.computed
+ ) {
+ return;
+ }
+
+ let snapshotIndex: number | undefined;
+ let snapshot: string | undefined;
+ for (let i = 0; i < args.length; i++) {
+ const node = args[i];
+ if (node.type === 'TemplateLiteral') {
+ snapshotIndex = i;
+ snapshot = node.quasis[0].value.raw;
+ }
+ }
+ if (snapshot === undefined) {
+ return;
+ }
+
+ const parent = ancestors[ancestors.length - 1].node;
+ const startColumn =
+ isAwaitExpression(parent) && parent.loc
+ ? parent.loc.start.column
+ : callee.loc.start.column;
+
+ const useSpaces = !options?.useTabs;
+ snapshot = indent(
+ snapshot,
+ Math.ceil(
+ useSpaces
+ ? startColumn / (options?.tabWidth ?? 1)
+ : // Each tab is 2 characters.
+ startColumn / 2,
+ ),
+ useSpaces ? ' '.repeat(options?.tabWidth ?? 1) : '\t',
+ );
+
+ if (keepNode) {
+ (args[snapshotIndex!] as TemplateLiteral).quasis[0].value.raw = snapshot;
+ } else {
+ const replacementNode = templateLiteral(
+ [
+ templateElement({
+ raw: snapshot,
+ }),
+ ],
+ [],
+ );
+ args[snapshotIndex!] = replacementNode;
+ }
+ });
+};
+
+const groupSnapshotsBy =
+ (createKey: (inlineSnapshot: InlineSnapshot) => string) =>
+ (snapshots: Array) =>
+ snapshots.reduce>>(
+ (object, inlineSnapshot) => {
+ const key = createKey(inlineSnapshot);
+ return {...object, [key]: (object[key] || []).concat(inlineSnapshot)};
+ },
+ {},
+ );
+
+const groupSnapshotsByFrame = groupSnapshotsBy(({frame: {line, column}}) =>
+ typeof line === 'number' && typeof column === 'number'
+ ? `${line}:${column - 1}`
+ : '',
+);
+export const groupSnapshotsByFile = groupSnapshotsBy(({frame: {file}}) => file);
+
+const traverseAst = (
+ snapshots: Array,
+ ast: File | Program,
+ snapshotMatcherNames: Array,
+) => {
+ const groupedSnapshots = groupSnapshotsByFrame(snapshots);
+ const remainingSnapshots = new Set(snapshots.map(({snapshot}) => snapshot));
+
+ traverseFast(ast, (node: Node) => {
+ if (node.type !== 'CallExpression') return;
+
+ const {arguments: args, callee} = node;
+ if (
+ callee.type !== 'MemberExpression' ||
+ callee.property.type !== 'Identifier' ||
+ callee.property.loc == null
+ ) {
+ return;
+ }
+ const {line, column} = callee.property.loc.start;
+ const snapshotsForFrame = groupedSnapshots[`${line}:${column}`];
+ if (!snapshotsForFrame) {
+ return;
+ }
+ if (snapshotsForFrame.length > 1) {
+ throw new Error(
+ 'Jest: Multiple inline snapshots for the same call are not supported.',
+ );
+ }
+ const inlineSnapshot = snapshotsForFrame[0];
+ inlineSnapshot.node = node;
+
+ snapshotMatcherNames.push(callee.property.name);
+
+ const snapshotIndex = args.findIndex(
+ ({type}) => type === 'TemplateLiteral' || type === 'StringLiteral',
+ );
+
+ const {snapshot} = inlineSnapshot;
+ remainingSnapshots.delete(snapshot);
+ const replacementNode = templateLiteral(
+ [templateElement({raw: escapeBacktickString(snapshot)})],
+ [],
+ );
+
+ if (snapshotIndex > -1) {
+ args[snapshotIndex] = replacementNode;
+ } else {
+ args.push(replacementNode);
+ }
+ });
+
+ if (remainingSnapshots.size > 0) {
+ throw new Error("Jest: Couldn't locate all inline snapshots.");
+ }
+};
diff --git a/packages/jest-snapshot/src/worker.ts b/packages/jest-snapshot/src/worker.ts
new file mode 100644
index 000000000000..74dfab8fdcf2
--- /dev/null
+++ b/packages/jest-snapshot/src/worker.ts
@@ -0,0 +1,63 @@
+/**
+ * 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 {runAsWorker} from 'synckit';
+import {processPrettierAst} from './utils';
+
+let prettier: typeof import('prettier');
+
+runAsWorker(
+ async (
+ prettierPath: string,
+ filepath: string,
+ sourceFileWithSnapshots: string,
+ snapshotMatcherNames: Array,
+ ) => {
+ // @ts-expect-error requireOutside
+ prettier ??= requireOutside(/*webpackIgnore: true*/ prettierPath);
+
+ const config = await prettier.resolveConfig(filepath, {
+ editorconfig: true,
+ });
+
+ const inferredParser: string | null =
+ (typeof config?.parser === 'string' && config.parser) ||
+ (await prettier.getFileInfo(filepath)).inferredParser;
+
+ if (!inferredParser) {
+ throw new Error(`Could not infer Prettier parser for file ${filepath}`);
+ }
+
+ sourceFileWithSnapshots = await prettier.format(sourceFileWithSnapshots, {
+ ...config,
+ filepath,
+ parser: inferredParser,
+ });
+
+ // @ts-expect-error private API
+ const {ast} = await prettier.__debug.parse(sourceFileWithSnapshots, {
+ ...config,
+ filepath,
+ originalText: sourceFileWithSnapshots,
+ parser: inferredParser,
+ });
+ processPrettierAst(ast, config, snapshotMatcherNames, true);
+ // 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 /** @ts-expect-error private API */ (
+ await prettier.__debug.formatAST(ast, {
+ ...config,
+ filepath,
+ originalText: sourceFileWithSnapshots,
+ parser: inferredParser,
+ })
+ ).formatted;
+ },
+);
diff --git a/scripts/buildUtils.mjs b/scripts/buildUtils.mjs
index 50f78804e7cd..e4cf23490d19 100644
--- a/scripts/buildUtils.mjs
+++ b/scripts/buildUtils.mjs
@@ -217,6 +217,8 @@ export function createWebpackConfigs() {
}
: pkg.name === 'jest-repl'
? {repl: path.resolve(packageDir, './src/cli/repl.ts')}
+ : pkg.name === 'jest-snapshot'
+ ? {worker: path.resolve(packageDir, './src/worker.ts')}
: {};
const extraEntryPoints =
diff --git a/yarn.lock b/yarn.lock
index 559ac8d0c59e..fbcd0d08a277 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3888,6 +3888,20 @@ __metadata:
languageName: node
linkType: hard
+"@pkgr/utils@npm:^2.3.1":
+ version: 2.4.2
+ resolution: "@pkgr/utils@npm:2.4.2"
+ dependencies:
+ cross-spawn: ^7.0.3
+ fast-glob: ^3.3.0
+ is-glob: ^4.0.3
+ open: ^9.1.0
+ picocolors: ^1.0.0
+ tslib: ^2.6.0
+ checksum: 24e04c121269317d259614cd32beea3af38277151c4002df5883c4be920b8e3490bb897748e844f9d46bf68230f86dabd4e8f093773130e7e60529a769a132fc
+ languageName: node
+ linkType: hard
+
"@pnpm/config.env-replace@npm:^1.1.0":
version: 1.1.0
resolution: "@pnpm/config.env-replace@npm:1.1.0"
@@ -5155,7 +5169,7 @@ __metadata:
languageName: node
linkType: hard
-"@types/prettier@npm:^2.1.5":
+"@types/prettier-v2@npm:@types/prettier@^2.1.5, @types/prettier@npm:^2.1.5":
version: 2.7.3
resolution: "@types/prettier@npm:2.7.3"
checksum: 705384209cea6d1433ff6c187c80dcc0b95d99d5c5ce21a46a9a58060c527973506822e428789d842761e0280d25e3359300f017fbe77b9755bc772ab3dc2f83
@@ -6738,6 +6752,13 @@ __metadata:
languageName: node
linkType: hard
+"big-integer@npm:^1.6.44":
+ version: 1.6.51
+ resolution: "big-integer@npm:1.6.51"
+ checksum: 3d444173d1b2e20747e2c175568bedeebd8315b0637ea95d75fd27830d3b8e8ba36c6af40374f36bdaea7b5de376dcada1b07587cb2a79a928fccdb6e6e3c518
+ languageName: node
+ linkType: hard
+
"big.js@npm:^5.2.2":
version: 5.2.2
resolution: "big.js@npm:5.2.2"
@@ -6846,6 +6867,15 @@ __metadata:
languageName: node
linkType: hard
+"bplist-parser@npm:^0.2.0":
+ version: 0.2.0
+ resolution: "bplist-parser@npm:0.2.0"
+ dependencies:
+ big-integer: ^1.6.44
+ checksum: d5339dd16afc51de6c88f88f58a45b72ed6a06aa31f5557d09877575f220b7c1d3fbe375da0b62e6a10d4b8ed80523567e351f24014f5bc886ad523758142cdd
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"
@@ -6947,6 +6977,15 @@ __metadata:
languageName: node
linkType: hard
+"bundle-name@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "bundle-name@npm:3.0.0"
+ dependencies:
+ run-applescript: ^5.0.0
+ checksum: edf2b1fbe6096ed32e7566947ace2ea937ee427391744d7510a2880c4b9a5b3543d3f6c551236a29e5c87d3195f8e2912516290e638c15bcbede7b37cc375615
+ languageName: node
+ linkType: hard
+
"byte-size@npm:^7.0.1":
version: 7.0.1
resolution: "byte-size@npm:7.0.1"
@@ -8490,6 +8529,28 @@ __metadata:
languageName: node
linkType: hard
+"default-browser-id@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "default-browser-id@npm:3.0.0"
+ dependencies:
+ bplist-parser: ^0.2.0
+ untildify: ^4.0.0
+ checksum: 279c7ad492542e5556336b6c254a4eaf31b2c63a5433265655ae6e47301197b6cfb15c595a6fdc6463b2ff8e1a1a1ed3cba56038a60e1527ba4ab1628c6b9941
+ languageName: node
+ linkType: hard
+
+"default-browser@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "default-browser@npm:4.0.0"
+ dependencies:
+ bundle-name: ^3.0.0
+ default-browser-id: ^3.0.0
+ execa: ^7.1.1
+ titleize: ^3.0.0
+ checksum: 40c5af984799042b140300be5639c9742599bda76dc9eba5ac9ad5943c83dd36cebc4471eafcfddf8e0ec817166d5ba89d56f08e66a126c7c7908a179cead1a7
+ languageName: node
+ linkType: hard
+
"default-gateway@npm:^6.0.3":
version: 6.0.3
resolution: "default-gateway@npm:6.0.3"
@@ -8522,6 +8583,13 @@ __metadata:
languageName: node
linkType: hard
+"define-lazy-prop@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "define-lazy-prop@npm:3.0.0"
+ checksum: 54884f94caac0791bf6395a3ec530ce901cf71c47b0196b8754f3fd17edb6c0e80149c1214429d851873bb0d689dbe08dcedbb2306dc45c8534a5934723851b6
+ languageName: node
+ linkType: hard
+
"define-properties@npm:^1.1.3, define-properties@npm:^1.1.4, define-properties@npm:^1.2.0":
version: 1.2.0
resolution: "define-properties@npm:1.2.0"
@@ -9829,6 +9897,23 @@ __metadata:
languageName: node
linkType: hard
+"execa@npm:^7.1.1":
+ version: 7.2.0
+ resolution: "execa@npm:7.2.0"
+ dependencies:
+ cross-spawn: ^7.0.3
+ get-stream: ^6.0.1
+ human-signals: ^4.3.0
+ is-stream: ^3.0.0
+ merge-stream: ^2.0.0
+ npm-run-path: ^5.1.0
+ onetime: ^6.0.0
+ signal-exit: ^3.0.7
+ strip-final-newline: ^3.0.0
+ checksum: 14fd17ba0ca8c87b277584d93b1d9fc24f2a65e5152b31d5eb159a3b814854283eaae5f51efa9525e304447e2f757c691877f7adff8fde5746aae67eb1edd1cc
+ languageName: node
+ linkType: hard
+
"exit@npm:^0.1.2":
version: 0.1.2
resolution: "exit@npm:0.1.2"
@@ -11472,6 +11557,13 @@ __metadata:
languageName: node
linkType: hard
+"human-signals@npm:^4.3.0":
+ version: 4.3.1
+ resolution: "human-signals@npm:4.3.1"
+ checksum: 6f12958df3f21b6fdaf02d90896c271df00636a31e2bbea05bddf817a35c66b38a6fdac5863e2df85bd52f34958997f1f50350ff97249e1dff8452865d5235d1
+ languageName: node
+ linkType: hard
+
"humanize-ms@npm:^1.2.1":
version: 1.2.1
resolution: "humanize-ms@npm:1.2.1"
@@ -11929,6 +12021,15 @@ __metadata:
languageName: node
linkType: hard
+"is-docker@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "is-docker@npm:3.0.0"
+ bin:
+ is-docker: cli.js
+ checksum: b698118f04feb7eaf3338922bd79cba064ea54a1c3db6ec8c0c8d8ee7613e7e5854d802d3ef646812a8a3ace81182a085dfa0a71cc68b06f3fa794b9783b3c90
+ languageName: node
+ linkType: hard
+
"is-extendable@npm:^0.1.0":
version: 0.1.1
resolution: "is-extendable@npm:0.1.1"
@@ -11987,6 +12088,17 @@ __metadata:
languageName: node
linkType: hard
+"is-inside-container@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "is-inside-container@npm:1.0.0"
+ dependencies:
+ is-docker: ^3.0.0
+ bin:
+ is-inside-container: cli.js
+ checksum: c50b75a2ab66ab3e8b92b3bc534e1ea72ca25766832c0623ac22d134116a98bcf012197d1caabe1d1c4bd5f84363d4aa5c36bb4b585fbcaf57be172cd10a1a03
+ languageName: node
+ linkType: hard
+
"is-installed-globally@npm:^0.4.0":
version: 0.4.0
resolution: "is-installed-globally@npm:0.4.0"
@@ -12199,6 +12311,13 @@ __metadata:
languageName: node
linkType: hard
+"is-stream@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "is-stream@npm:3.0.0"
+ checksum: 172093fe99119ffd07611ab6d1bcccfe8bc4aa80d864b15f43e63e54b7abc71e779acd69afdb854c4e2a67fdc16ae710e370eda40088d1cfc956a50ed82d8f16
+ languageName: node
+ linkType: hard
+
"is-string@npm:^1.0.5, is-string@npm:^1.0.7":
version: 1.0.7
resolution: "is-string@npm:1.0.7"
@@ -12959,7 +13078,7 @@ __metadata:
"@types/babel__core": ^7.1.14
"@types/graceful-fs": ^4.1.3
"@types/natural-compare": ^1.4.0
- "@types/prettier": ^2.1.5
+ "@types/prettier-v2": "npm:@types/prettier@^2.1.5"
"@types/semver": ^7.1.0
ansi-regex: ^5.0.1
ansi-styles: ^5.0.0
@@ -12973,9 +13092,11 @@ __metadata:
jest-message-util: "workspace:*"
jest-util: "workspace:*"
natural-compare: ^1.4.0
- prettier: ^2.1.1
+ prettier: ^3.0.3
+ prettier-v2: "npm:prettier@^2.1.5"
pretty-format: "workspace:*"
semver: ^7.5.3
+ synckit: ^0.8.5
tsd-lite: ^0.8.0
languageName: unknown
linkType: soft
@@ -15190,6 +15311,13 @@ __metadata:
languageName: node
linkType: hard
+"mimic-fn@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "mimic-fn@npm:4.0.0"
+ checksum: 995dcece15ee29aa16e188de6633d43a3db4611bcf93620e7e62109ec41c79c0f34277165b8ce5e361205049766e371851264c21ac64ca35499acb5421c2ba56
+ languageName: node
+ linkType: hard
+
"mimic-response@npm:^3.1.0":
version: 3.1.0
resolution: "mimic-response@npm:3.1.0"
@@ -15815,6 +15943,15 @@ __metadata:
languageName: node
linkType: hard
+"npm-run-path@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "npm-run-path@npm:5.1.0"
+ dependencies:
+ path-key: ^4.0.0
+ checksum: dc184eb5ec239d6a2b990b43236845332ef12f4e0beaa9701de724aa797fe40b6bbd0157fb7639d24d3ab13f5d5cf22d223a19c6300846b8126f335f788bee66
+ languageName: node
+ linkType: hard
+
"npm-to-yarn@npm:^2.0.0":
version: 2.0.0
resolution: "npm-to-yarn@npm:2.0.0"
@@ -16010,6 +16147,15 @@ __metadata:
languageName: node
linkType: hard
+"onetime@npm:^6.0.0":
+ version: 6.0.0
+ resolution: "onetime@npm:6.0.0"
+ dependencies:
+ mimic-fn: ^4.0.0
+ checksum: 0846ce78e440841335d4e9182ef69d5762e9f38aa7499b19f42ea1c4cd40f0b4446094c455c713f9adac3f4ae86f613bb5e30c99e52652764d06a89f709b3788
+ languageName: node
+ linkType: hard
+
"open@npm:^6.2.0":
version: 6.4.0
resolution: "open@npm:6.4.0"
@@ -16030,6 +16176,18 @@ __metadata:
languageName: node
linkType: hard
+"open@npm:^9.1.0":
+ version: 9.1.0
+ resolution: "open@npm:9.1.0"
+ dependencies:
+ default-browser: ^4.0.0
+ define-lazy-prop: ^3.0.0
+ is-inside-container: ^1.0.0
+ is-wsl: ^2.2.0
+ checksum: 3993c0f61d51fed8ac290e99c9c3cf45d3b6cfb3e2aa2b74cafd312c3486c22fd81df16ac8f3ab91dd8a4e3e729a16fc2480cfc406c4833416cf908acf1ae7c9
+ languageName: node
+ linkType: hard
+
"opener@npm:^1.5.2":
version: 1.5.2
resolution: "opener@npm:1.5.2"
@@ -16470,6 +16628,13 @@ __metadata:
languageName: node
linkType: hard
+"path-key@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "path-key@npm:4.0.0"
+ checksum: 8e6c314ae6d16b83e93032c61020129f6f4484590a777eed709c4a01b50e498822b00f76ceaf94bc64dbd90b327df56ceadce27da3d83393790f1219e07721d7
+ languageName: node
+ linkType: hard
+
"path-parse@npm:^1.0.6, path-parse@npm:^1.0.7":
version: 1.0.7
resolution: "path-parse@npm:1.0.7"
@@ -17123,7 +17288,7 @@ __metadata:
languageName: node
linkType: hard
-"prettier@npm:^2.1.1, prettier@npm:^2.8.3":
+"prettier-v2@npm:prettier@^2.1.5, prettier@npm:^2.1.1, prettier@npm:^2.8.3":
version: 2.8.8
resolution: "prettier@npm:2.8.8"
bin:
@@ -17132,6 +17297,15 @@ __metadata:
languageName: node
linkType: hard
+"prettier@npm:^3.0.3":
+ version: 3.0.3
+ resolution: "prettier@npm:3.0.3"
+ bin:
+ prettier: bin/prettier.cjs
+ checksum: e10b9af02b281f6c617362ebd2571b1d7fc9fb8a3bd17e371754428cda992e5e8d8b7a046e8f7d3e2da1dcd21aa001e2e3c797402ebb6111b5cd19609dd228e0
+ languageName: node
+ linkType: hard
+
"pretty-bytes@npm:^5.3.0":
version: 5.6.0
resolution: "pretty-bytes@npm:5.6.0"
@@ -18490,6 +18664,15 @@ __metadata:
languageName: node
linkType: hard
+"run-applescript@npm:^5.0.0":
+ version: 5.0.0
+ resolution: "run-applescript@npm:5.0.0"
+ dependencies:
+ execa: ^5.0.0
+ checksum: d00c2dbfa5b2d774de7451194b8b125f40f65fc183de7d9dcae97f57f59433586d3c39b9001e111c38bfa24c3436c99df1bb4066a2a0c90d39a8c4cd6889af77
+ languageName: node
+ linkType: hard
+
"run-async@npm:^2.4.0":
version: 2.4.1
resolution: "run-async@npm:2.4.1"
@@ -19477,6 +19660,13 @@ __metadata:
languageName: node
linkType: hard
+"strip-final-newline@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "strip-final-newline@npm:3.0.0"
+ checksum: 23ee263adfa2070cd0f23d1ac14e2ed2f000c9b44229aec9c799f1367ec001478469560abefd00c5c99ee6f0b31c137d53ec6029c53e9f32a93804e18c201050
+ languageName: node
+ linkType: hard
+
"strip-indent@npm:^3.0.0":
version: 3.0.0
resolution: "strip-indent@npm:3.0.0"
@@ -19613,6 +19803,16 @@ __metadata:
languageName: node
linkType: hard
+"synckit@npm:^0.8.5":
+ version: 0.8.5
+ resolution: "synckit@npm:0.8.5"
+ dependencies:
+ "@pkgr/utils": ^2.3.1
+ tslib: ^2.5.0
+ checksum: 8a9560e5d8f3d94dc3cf5f7b9c83490ffa30d320093560a37b88f59483040771fd1750e76b9939abfbb1b5a23fd6dfbae77f6b338abffe7cae7329cd9b9bb86b
+ languageName: node
+ linkType: hard
+
"tapable@npm:^1.0.0":
version: 1.1.3
resolution: "tapable@npm:1.1.3"
@@ -19819,6 +20019,13 @@ __metadata:
languageName: node
linkType: hard
+"titleize@npm:^3.0.0":
+ version: 3.0.0
+ resolution: "titleize@npm:3.0.0"
+ checksum: 71fbbeabbfb36ccd840559f67f21e356e1d03da2915b32d2ae1a60ddcc13a124be2739f696d2feb884983441d159a18649e8d956648d591bdad35c430a6b6d28
+ languageName: node
+ linkType: hard
+
"tmp@npm:^0.0.33":
version: 0.0.33
resolution: "tmp@npm:0.0.33"
@@ -20043,10 +20250,10 @@ __metadata:
languageName: node
linkType: hard
-"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.6.0":
- version: 2.6.0
- resolution: "tslib@npm:2.6.0"
- checksum: c01066038f950016a18106ddeca4649b4d76caa76ec5a31e2a26e10586a59fceb4ee45e96719bf6c715648e7c14085a81fee5c62f7e9ebee68e77a5396e5538f
+"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.5.0, tslib@npm:^2.6.0":
+ version: 2.6.2
+ resolution: "tslib@npm:2.6.2"
+ checksum: 329ea56123005922f39642318e3d1f0f8265d1e7fcb92c633e0809521da75eeaca28d2cf96d7248229deb40e5c19adf408259f4b9640afd20d13aecc1430f3ad
languageName: node
linkType: hard
@@ -20532,6 +20739,13 @@ __metadata:
languageName: node
linkType: hard
+"untildify@npm:^4.0.0":
+ version: 4.0.0
+ resolution: "untildify@npm:4.0.0"
+ checksum: 39ced9c418a74f73f0a56e1ba4634b4d959422dff61f4c72a8e39f60b99380c1b45ed776fbaa0a4101b157e4310d873ad7d114e8534ca02609b4916bb4187fb9
+ languageName: node
+ linkType: hard
+
"upath@npm:^1.2.0":
version: 1.2.0
resolution: "upath@npm:1.2.0"