'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); exports.saveInlineSnapshots = void 0; var _fs = _interopRequireDefault(require('fs')); var _path = _interopRequireDefault(require('path')); var _semver = _interopRequireDefault(require('semver')); var _types = require('@babel/types'); var _utils = require('./utils'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : {default: obj}; } var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var jestWriteFile = global[Symbol.for('jest-native-write-file')] || _fs.default.writeFileSync; function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat( Object.getOwnPropertySymbols(source).filter(function(sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; }) ); } ownKeys.forEach(function(key) { _defineProperty(target, key, source[key]); }); } return target; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } var Symbol = global['jest-symbol-do-not-touch'] || global.Symbol; var jestReadFile = global[Symbol.for('jest-native-read-file')] || _fs.default.readFileSync; const saveInlineSnapshots = (snapshots, prettier, babelTraverse) => { if (!prettier) { throw new Error( `Jest: Inline Snapshots requires Prettier.\n` + `Please ensure "prettier" is installed in your project.` ); } // Custom parser API was added in 1.5.0 if (_semver.default.lt(prettier.version, '1.5.0')) { throw new Error( `Jest: Inline Snapshots require prettier>=1.5.0.\n` + `Please upgrade "prettier".` ); } const snapshotsByFile = groupSnapshotsByFile(snapshots); var _arr = Object.keys(snapshotsByFile); for (var _i = 0; _i < _arr.length; _i++) { const sourceFilePath = _arr[_i]; saveSnapshotsForFile( snapshotsByFile[sourceFilePath], sourceFilePath, prettier, babelTraverse ); } }; exports.saveInlineSnapshots = saveInlineSnapshots; const saveSnapshotsForFile = ( snapshots, sourceFilePath, prettier, babelTraverse ) => { const sourceFile = jestReadFile(sourceFilePath, 'utf8'); // Resolve project configuration. // For older versions of Prettier, do not load configuration. const config = prettier.resolveConfig ? prettier.resolveConfig.sync(sourceFilePath, { editorconfig: true }) : null; // Detect the parser for the test file. // For older versions of Prettier, fallback to a simple parser detection. const inferredParser = prettier.getFileInfo ? prettier.getFileInfo.sync(sourceFilePath).inferredParser : (config && config.parser) || simpleDetectParser(sourceFilePath); // Insert snapshots using the custom parser API. After insertion, 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). const newSourceFile = prettier.format( sourceFile, _objectSpread({}, config, { filepath: sourceFilePath, parser: createInsertionParser(snapshots, inferredParser, babelTraverse) }) ); // Format the snapshots using the custom parser API. const formattedNewSourceFile = prettier.format( newSourceFile, _objectSpread({}, config, { filepath: sourceFilePath, parser: createFormattingParser(inferredParser, babelTraverse) }) ); if (formattedNewSourceFile !== sourceFile) { jestWriteFile(sourceFilePath, formattedNewSourceFile); } }; const groupSnapshotsBy = createKey => snapshots => snapshots.reduce((object, inlineSnapshot) => { const key = createKey(inlineSnapshot); return _objectSpread({}, 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, numIndents, indentation) => { const lines = snapshot.split('\n'); // Prevent re-identation 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) { // 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; } else { // The last line should be placed on the same level as the expect call. return indentation.repeat(numIndents) + line; } }) .join('\n'); }; const getAst = (parsers, inferredParser, text) => { // Flow uses a 'Program' parent node, babel expects a 'File'. let ast = parsers[inferredParser](text); if (ast.type !== 'File') { ast = (0, _types.file)(ast, ast.comments, ast.tokens); delete ast.program.comments; } return ast; }; // This parser inserts snapshots into the AST. const createInsertionParser = (snapshots, inferredParser, babelTraverse) => ( text, parsers, options ) => { // Workaround for https://github.com/prettier/prettier/issues/3150 options.parser = inferredParser; const groupedSnapshots = groupSnapshotsByFrame(snapshots); const remainingSnapshots = new Set(snapshots.map(({snapshot}) => snapshot)); const ast = getAst(parsers, inferredParser, text); babelTraverse(ast, { CallExpression({node: {arguments: args, callee}}) { if ( callee.type !== 'MemberExpression' || callee.property.type !== 'Identifier' ) { return; } const _callee$property$loc$ = callee.property.loc.start, line = _callee$property$loc$.line, column = _callee$property$loc$.column; 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 snapshotIndex = args.findIndex( ({type}) => type === 'TemplateLiteral' ); const values = snapshotsForFrame.map(({snapshot}) => { remainingSnapshots.delete(snapshot); return (0, _types.templateLiteral)( [ (0, _types.templateElement)({ raw: (0, _utils.escapeBacktickString)(snapshot) }) ], [] ); }); const replacementNode = values[0]; if (snapshotIndex > -1) { args[snapshotIndex] = replacementNode; } else { args.push(replacementNode); } } }); if (remainingSnapshots.size) { throw new Error(`Jest: Couldn't locate all inline snapshots.`); } return ast; }; // This parser formats snapshots to the correct indentation. const createFormattingParser = (inferredParser, babelTraverse) => ( text, parsers, options ) => { // Workaround for https://github.com/prettier/prettier/issues/3150 options.parser = inferredParser; const ast = getAst(parsers, inferredParser, text); babelTraverse(ast, { CallExpression({node: {arguments: args, callee}}) { if ( callee.type !== 'MemberExpression' || callee.property.type !== 'Identifier' || callee.property.name !== 'toMatchInlineSnapshot' || !callee.loc || callee.computed ) { return; } let snapshotIndex; let snapshot; 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 || snapshotIndex === undefined) { return; } const useSpaces = !options.useTabs; snapshot = indent( snapshot, Math.ceil( useSpaces ? callee.loc.start.column / options.tabWidth : callee.loc.start.column / 2 // Each tab is 2 characters. ), useSpaces ? ' '.repeat(options.tabWidth) : '\t' ); const replacementNode = (0, _types.templateLiteral)( [ (0, _types.templateElement)({ raw: snapshot }) ], [] ); args[snapshotIndex] = replacementNode; } }); return ast; }; const simpleDetectParser = filePath => { const extname = _path.default.extname(filePath); if (/tsx?$/.test(extname)) { return 'typescript'; } return 'babylon'; };