324 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			324 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| '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');
 | |
|   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';
 | |
| };
 |