diff --git a/packages/jest-snapshot/package.json b/packages/jest-snapshot/package.json index 51feafcc1477..086395b04e54 100644 --- a/packages/jest-snapshot/package.json +++ b/packages/jest-snapshot/package.json @@ -37,9 +37,9 @@ "jest-matcher-utils": "workspace:*", "jest-message-util": "workspace:*", "jest-util": "workspace:*", + "make-synchronized": "^0.4.2", "pretty-format": "workspace:*", - "semver": "^7.5.3", - "synckit": "^0.9.0" + "semver": "^7.5.3" }, "devDependencies": { "@babel/preset-flow": "^7.7.2", diff --git a/packages/jest-snapshot/src/InlineSnapshots.ts b/packages/jest-snapshot/src/InlineSnapshots.ts index f00860f93c4d..83ac25db02c0 100644 --- a/packages/jest-snapshot/src/InlineSnapshots.ts +++ b/packages/jest-snapshot/src/InlineSnapshots.ts @@ -5,54 +5,38 @@ * 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 {runPrettier, runPrettier2} from './prettier'; import type {InlineSnapshot} from './types'; -import { - groupSnapshotsByFile, - processInlineSnapshotsWithBabel, - processPrettierAst, -} from './utils'; +import {groupSnapshotsByFile, processInlineSnapshotsWithBabel} from './utils'; -type Prettier = typeof import('prettier-v2'); -type WorkerFn = ( - prettierPath: string, - filepath: string, - sourceFileWithSnapshots: string, - snapshotMatcherNames: Array, -) => string; +type Prettier = typeof runPrettier; +type Prettier2 = typeof import('prettier-v2'); -const cachedPrettier = new Map(); +const cachedPrettier = new Map(); export function saveInlineSnapshots( snapshots: Array, rootDir: string, prettierPath: string | null, ): void { - let prettier: Prettier | undefined = prettierPath - ? (cachedPrettier.get(`module|${prettierPath}`) as Prettier) + let prettier: Prettier2 | undefined = prettierPath + ? (cachedPrettier.get(`module|${prettierPath}`) as Prettier2) : undefined; - let workerFn: WorkerFn | undefined = prettierPath - ? (cachedPrettier.get(`worker|${prettierPath}`) as WorkerFn) + let workerFn: Prettier | undefined = prettierPath + ? (cachedPrettier.get(`worker|${prettierPath}`) as Prettier) : undefined; if (prettierPath && !prettier) { try { prettier = // @ts-expect-error requireOutside - requireOutside(prettierPath) as Prettier; + requireOutside(prettierPath) as Prettier2; cachedPrettier.set(`module|${prettierPath}`, prettier); if (semver.gte(prettier.version, '3.0.0')) { - workerFn = createSyncFn( - require.resolve(/*webpackIgnore: true*/ './worker'), - ) as WorkerFn; + workerFn = runPrettier; cachedPrettier.set(`worker|${prettierPath}`, workerFn); } } catch (error) { @@ -86,7 +70,7 @@ export function saveInlineSnapshots( snapshotMatcherNames, ); } else if (prettier && semver.gte(prettier.version, '1.5.0')) { - newSourceFile = runPrettier( + newSourceFile = runPrettier2( prettier, sourceFilePath, sourceFileWithSnapshots, @@ -99,73 +83,3 @@ export function saveInlineSnapshots( } } } - -const runPrettier = ( - prettier: Prettier, - sourceFilePath: string, - sourceFileWithSnapshots: string, - snapshotMatcherNames: Array, -) => { - // 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, - 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'; -}; diff --git a/packages/jest-snapshot/src/prettier.ts b/packages/jest-snapshot/src/prettier.ts new file mode 100644 index 000000000000..a574cfac6868 --- /dev/null +++ b/packages/jest-snapshot/src/prettier.ts @@ -0,0 +1,145 @@ +/** + * 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 {makeSynchronizedFunction} from 'make-synchronized'; +import type { + CustomParser as PrettierCustomParser, + BuiltInParserName as PrettierParserName, +} from 'prettier-v2'; +import {processPrettierAst} from './utils'; + +const simpleDetectParser = (filePath: string): PrettierParserName => { + const extname = path.extname(filePath); + if (/\.tsx?$/.test(extname)) { + return 'typescript'; + } + return 'babel'; +}; + +// This parser formats snapshots to the correct indentation. +const createFormattingParser = + ( + snapshotMatcherNames: Array, + 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; + }; + +async function getInferredParser(filepath: string) { + const fileInfo = await prettier.getFileInfo(filepath); + return fileInfo.inferredParser; +} + +let prettier: typeof import('prettier'); +export const runPrettier = makeSynchronizedFunction( + __filename, + async function runPrettierAsync( + prettierPath: string, + filepath: string, + sourceFileWithSnapshots: string, + snapshotMatcherNames: Array, + ): Promise { + // @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 getInferredParser(filepath); + + 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. + // @ts-expect-error private API + const formatAST = await prettier.__debug.formatAST(ast, { + ...config, + filepath, + originalText: sourceFileWithSnapshots, + parser: inferredParser, + }); + return formatAST.formatted; + }, + 'runPrettier', +); + +export function runPrettier2( + prettier: typeof import('prettier-v2'), + 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), + }, + ); +} diff --git a/packages/jest-snapshot/src/worker.ts b/packages/jest-snapshot/src/worker.ts deleted file mode 100644 index 027d498ddd60..000000000000 --- a/packages/jest-snapshot/src/worker.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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'); - -async function getInferredParser(filepath: string) { - const fileInfo = await prettier.getFileInfo(filepath); - - return fileInfo.inferredParser; -} - -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 getInferredParser(filepath); - - 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. - // @ts-expect-error private API - const formatAST = await prettier.__debug.formatAST(ast, { - ...config, - filepath, - originalText: sourceFileWithSnapshots, - parser: inferredParser, - }); - return formatAST.formatted; - }, -); diff --git a/scripts/buildUtils.mjs b/scripts/buildUtils.mjs index a92a16df950b..dfe29358f501 100644 --- a/scripts/buildUtils.mjs +++ b/scripts/buildUtils.mjs @@ -231,7 +231,12 @@ export function createBuildConfigs() { : pkg.name === 'jest-repl' ? {repl: path.resolve(packageDir, './src/cli/repl.ts')} : pkg.name === 'jest-snapshot' - ? {worker: path.resolve(packageDir, './src/worker.ts')} + ? { + prettier: path.resolve( + packageDir, + './src/prettier.ts', + ), + } : {}; const extraEntryPoints = diff --git a/yarn.lock b/yarn.lock index 5d52ce6644e8..d28e40015d1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13977,11 +13977,11 @@ __metadata: jest-matcher-utils: "workspace:*" jest-message-util: "workspace:*" jest-util: "workspace:*" + make-synchronized: ^0.4.2 prettier: ^3.0.3 prettier-v2: "npm:prettier@^2.1.5" pretty-format: "workspace:*" semver: ^7.5.3 - synckit: ^0.9.0 languageName: unknown linkType: soft @@ -14935,6 +14935,13 @@ __metadata: languageName: node linkType: hard +"make-synchronized@npm:^0.4.2": + version: 0.4.2 + resolution: "make-synchronized@npm:0.4.2" + checksum: fb2f5432559a638e3e126c3b85c474d9dc95bcf3d4f6578f2eb040b2d52a8bd09565de846e2d820ca69ca1aa19838cea01adfec03fd8690c66342100f38e695b + languageName: node + linkType: hard + "makeerror@npm:1.0.12": version: 1.0.12 resolution: "makeerror@npm:1.0.12" @@ -21045,7 +21052,7 @@ __metadata: languageName: node linkType: hard -"synckit@npm:^0.9.0, synckit@npm:^0.9.1": +"synckit@npm:^0.9.1": version: 0.9.2 resolution: "synckit@npm:0.9.2" dependencies: