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'; | ||
|  | }; |