ScriptTransformer.js 29 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.createScriptTransformer = createScriptTransformer;
  6. exports.createTranspilingRequire = createTranspilingRequire;
  7. function _crypto() {
  8. const data = require('crypto');
  9. _crypto = function () {
  10. return data;
  11. };
  12. return data;
  13. }
  14. function path() {
  15. const data = _interopRequireWildcard(require('path'));
  16. path = function () {
  17. return data;
  18. };
  19. return data;
  20. }
  21. function _core() {
  22. const data = require('@babel/core');
  23. _core = function () {
  24. return data;
  25. };
  26. return data;
  27. }
  28. function _babelPluginIstanbul() {
  29. const data = _interopRequireDefault(require('babel-plugin-istanbul'));
  30. _babelPluginIstanbul = function () {
  31. return data;
  32. };
  33. return data;
  34. }
  35. function _convertSourceMap() {
  36. const data = require('convert-source-map');
  37. _convertSourceMap = function () {
  38. return data;
  39. };
  40. return data;
  41. }
  42. function _fastJsonStableStringify() {
  43. const data = _interopRequireDefault(require('fast-json-stable-stringify'));
  44. _fastJsonStableStringify = function () {
  45. return data;
  46. };
  47. return data;
  48. }
  49. function fs() {
  50. const data = _interopRequireWildcard(require('graceful-fs'));
  51. fs = function () {
  52. return data;
  53. };
  54. return data;
  55. }
  56. function _pirates() {
  57. const data = require('pirates');
  58. _pirates = function () {
  59. return data;
  60. };
  61. return data;
  62. }
  63. function _slash() {
  64. const data = _interopRequireDefault(require('slash'));
  65. _slash = function () {
  66. return data;
  67. };
  68. return data;
  69. }
  70. function _writeFileAtomic() {
  71. const data = require('write-file-atomic');
  72. _writeFileAtomic = function () {
  73. return data;
  74. };
  75. return data;
  76. }
  77. function _jestHasteMap() {
  78. const data = _interopRequireDefault(require('jest-haste-map'));
  79. _jestHasteMap = function () {
  80. return data;
  81. };
  82. return data;
  83. }
  84. function _jestUtil() {
  85. const data = require('jest-util');
  86. _jestUtil = function () {
  87. return data;
  88. };
  89. return data;
  90. }
  91. var _enhanceUnexpectedTokenMessage = _interopRequireDefault(
  92. require('./enhanceUnexpectedTokenMessage')
  93. );
  94. var _runtimeErrorsAndWarnings = require('./runtimeErrorsAndWarnings');
  95. var _shouldInstrument = _interopRequireDefault(require('./shouldInstrument'));
  96. function _interopRequireDefault(obj) {
  97. return obj && obj.__esModule ? obj : {default: obj};
  98. }
  99. function _getRequireWildcardCache(nodeInterop) {
  100. if (typeof WeakMap !== 'function') return null;
  101. var cacheBabelInterop = new WeakMap();
  102. var cacheNodeInterop = new WeakMap();
  103. return (_getRequireWildcardCache = function (nodeInterop) {
  104. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  105. })(nodeInterop);
  106. }
  107. function _interopRequireWildcard(obj, nodeInterop) {
  108. if (!nodeInterop && obj && obj.__esModule) {
  109. return obj;
  110. }
  111. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  112. return {default: obj};
  113. }
  114. var cache = _getRequireWildcardCache(nodeInterop);
  115. if (cache && cache.has(obj)) {
  116. return cache.get(obj);
  117. }
  118. var newObj = {};
  119. var hasPropertyDescriptor =
  120. Object.defineProperty && Object.getOwnPropertyDescriptor;
  121. for (var key in obj) {
  122. if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
  123. var desc = hasPropertyDescriptor
  124. ? Object.getOwnPropertyDescriptor(obj, key)
  125. : null;
  126. if (desc && (desc.get || desc.set)) {
  127. Object.defineProperty(newObj, key, desc);
  128. } else {
  129. newObj[key] = obj[key];
  130. }
  131. }
  132. }
  133. newObj.default = obj;
  134. if (cache) {
  135. cache.set(obj, newObj);
  136. }
  137. return newObj;
  138. }
  139. function _defineProperty(obj, key, value) {
  140. if (key in obj) {
  141. Object.defineProperty(obj, key, {
  142. value: value,
  143. enumerable: true,
  144. configurable: true,
  145. writable: true
  146. });
  147. } else {
  148. obj[key] = value;
  149. }
  150. return obj;
  151. }
  152. // Use `require` to avoid TS rootDir
  153. const {version: VERSION} = require('../package.json');
  154. // This data structure is used to avoid recalculating some data every time that
  155. // we need to transform a file. Since ScriptTransformer is instantiated for each
  156. // file we need to keep this object in the local scope of this module.
  157. const projectCaches = new Map(); // To reset the cache for specific changesets (rather than package version).
  158. const CACHE_VERSION = '1';
  159. async function waitForPromiseWithCleanup(promise, cleanup) {
  160. try {
  161. await promise;
  162. } finally {
  163. cleanup();
  164. }
  165. }
  166. class ScriptTransformer {
  167. constructor(_config, _cacheFS) {
  168. _defineProperty(this, '_cache', void 0);
  169. _defineProperty(this, '_transformCache', new Map());
  170. _defineProperty(this, '_transformsAreLoaded', false);
  171. this._config = _config;
  172. this._cacheFS = _cacheFS;
  173. const configString = (0, _fastJsonStableStringify().default)(this._config);
  174. let projectCache = projectCaches.get(configString);
  175. if (!projectCache) {
  176. projectCache = {
  177. configString,
  178. ignorePatternsRegExp: calcIgnorePatternRegExp(this._config),
  179. transformRegExp: calcTransformRegExp(this._config),
  180. transformedFiles: new Map()
  181. };
  182. projectCaches.set(configString, projectCache);
  183. }
  184. this._cache = projectCache;
  185. }
  186. _buildCacheKeyFromFileInfo(
  187. fileData,
  188. filename,
  189. transformOptions,
  190. transformerCacheKey
  191. ) {
  192. if (transformerCacheKey) {
  193. return (0, _crypto().createHash)('md5')
  194. .update(transformerCacheKey)
  195. .update(CACHE_VERSION)
  196. .digest('hex');
  197. }
  198. return (0, _crypto().createHash)('md5')
  199. .update(fileData)
  200. .update(transformOptions.configString)
  201. .update(transformOptions.instrument ? 'instrument' : '')
  202. .update(filename)
  203. .update(CACHE_VERSION)
  204. .digest('hex');
  205. }
  206. _getCacheKey(fileData, filename, options) {
  207. const configString = this._cache.configString;
  208. const {transformer, transformerConfig = {}} =
  209. this._getTransformer(filename) || {};
  210. let transformerCacheKey = undefined;
  211. const transformOptions = {
  212. ...options,
  213. cacheFS: this._cacheFS,
  214. config: this._config,
  215. configString,
  216. transformerConfig
  217. };
  218. if (
  219. typeof (transformer === null || transformer === void 0
  220. ? void 0
  221. : transformer.getCacheKey) === 'function'
  222. ) {
  223. transformerCacheKey = transformer.getCacheKey(
  224. fileData,
  225. filename,
  226. transformOptions
  227. );
  228. }
  229. return this._buildCacheKeyFromFileInfo(
  230. fileData,
  231. filename,
  232. transformOptions,
  233. transformerCacheKey
  234. );
  235. }
  236. async _getCacheKeyAsync(fileData, filename, options) {
  237. const configString = this._cache.configString;
  238. const {transformer, transformerConfig = {}} =
  239. this._getTransformer(filename) || {};
  240. let transformerCacheKey = undefined;
  241. const transformOptions = {
  242. ...options,
  243. cacheFS: this._cacheFS,
  244. config: this._config,
  245. configString,
  246. transformerConfig
  247. };
  248. if (transformer) {
  249. const getCacheKey =
  250. transformer.getCacheKeyAsync || transformer.getCacheKey;
  251. if (typeof getCacheKey === 'function') {
  252. transformerCacheKey = await getCacheKey(
  253. fileData,
  254. filename,
  255. transformOptions
  256. );
  257. }
  258. }
  259. return this._buildCacheKeyFromFileInfo(
  260. fileData,
  261. filename,
  262. transformOptions,
  263. transformerCacheKey
  264. );
  265. }
  266. _createFolderFromCacheKey(filename, cacheKey) {
  267. const HasteMapClass = _jestHasteMap().default.getStatic(this._config);
  268. const baseCacheDir = HasteMapClass.getCacheFilePath(
  269. this._config.cacheDirectory,
  270. 'jest-transform-cache-' + this._config.name,
  271. VERSION
  272. ); // Create sub folders based on the cacheKey to avoid creating one
  273. // directory with many files.
  274. const cacheDir = path().join(baseCacheDir, cacheKey[0] + cacheKey[1]);
  275. const cacheFilenamePrefix = path()
  276. .basename(filename, path().extname(filename))
  277. .replace(/\W/g, '');
  278. const cachePath = (0, _slash().default)(
  279. path().join(cacheDir, cacheFilenamePrefix + '_' + cacheKey)
  280. );
  281. (0, _jestUtil().createDirectory)(cacheDir);
  282. return cachePath;
  283. }
  284. _getFileCachePath(filename, content, options) {
  285. const cacheKey = this._getCacheKey(content, filename, options);
  286. return this._createFolderFromCacheKey(filename, cacheKey);
  287. }
  288. async _getFileCachePathAsync(filename, content, options) {
  289. const cacheKey = await this._getCacheKeyAsync(content, filename, options);
  290. return this._createFolderFromCacheKey(filename, cacheKey);
  291. }
  292. _getTransformPath(filename) {
  293. const transformRegExp = this._cache.transformRegExp;
  294. if (!transformRegExp) {
  295. return undefined;
  296. }
  297. for (let i = 0; i < transformRegExp.length; i++) {
  298. if (transformRegExp[i][0].test(filename)) {
  299. return transformRegExp[i][1];
  300. }
  301. }
  302. return undefined;
  303. }
  304. async loadTransformers() {
  305. await Promise.all(
  306. this._config.transform.map(
  307. async ([, transformPath, transformerConfig]) => {
  308. let transformer = await (0, _jestUtil().requireOrImportModule)(
  309. transformPath
  310. );
  311. if (!transformer) {
  312. throw new Error(
  313. (0, _runtimeErrorsAndWarnings.makeInvalidTransformerError)(
  314. transformPath
  315. )
  316. );
  317. }
  318. if (typeof transformer.createTransformer === 'function') {
  319. transformer = transformer.createTransformer(transformerConfig);
  320. }
  321. if (
  322. typeof transformer.process !== 'function' &&
  323. typeof transformer.processAsync !== 'function'
  324. ) {
  325. throw new Error(
  326. (0, _runtimeErrorsAndWarnings.makeInvalidTransformerError)(
  327. transformPath
  328. )
  329. );
  330. }
  331. const res = {
  332. transformer,
  333. transformerConfig
  334. };
  335. this._transformCache.set(transformPath, res);
  336. }
  337. )
  338. );
  339. this._transformsAreLoaded = true;
  340. }
  341. _getTransformer(filename) {
  342. if (!this._transformsAreLoaded) {
  343. throw new Error(
  344. 'Jest: Transformers have not been loaded yet - make sure to run `loadTransformers` and wait for it to complete before starting to transform files'
  345. );
  346. }
  347. if (this._config.transform.length === 0) {
  348. return null;
  349. }
  350. const transformPath = this._getTransformPath(filename);
  351. if (!transformPath) {
  352. return null;
  353. }
  354. const cached = this._transformCache.get(transformPath);
  355. if (cached) {
  356. return cached;
  357. }
  358. throw new Error(
  359. `Jest was unable to load the transformer defined for ${filename}. This is a bug in Jest, please open up an issue`
  360. );
  361. }
  362. _instrumentFile(filename, input, canMapToInput, options) {
  363. const inputCode = typeof input === 'string' ? input : input.code;
  364. const inputMap = typeof input === 'string' ? null : input.map;
  365. const result = (0, _core().transformSync)(inputCode, {
  366. auxiliaryCommentBefore: ' istanbul ignore next ',
  367. babelrc: false,
  368. caller: {
  369. name: '@jest/transform',
  370. supportsDynamicImport: options.supportsDynamicImport,
  371. supportsExportNamespaceFrom: options.supportsExportNamespaceFrom,
  372. supportsStaticESM: options.supportsStaticESM,
  373. supportsTopLevelAwait: options.supportsTopLevelAwait
  374. },
  375. configFile: false,
  376. filename,
  377. plugins: [
  378. [
  379. _babelPluginIstanbul().default,
  380. {
  381. compact: false,
  382. // files outside `cwd` will not be instrumented
  383. cwd: this._config.rootDir,
  384. exclude: [],
  385. extension: false,
  386. inputSourceMap: inputMap,
  387. useInlineSourceMaps: false
  388. }
  389. ]
  390. ],
  391. sourceMaps: canMapToInput ? 'both' : false
  392. });
  393. if (result && result.code) {
  394. return result;
  395. }
  396. return input;
  397. }
  398. _buildTransformResult(
  399. filename,
  400. cacheFilePath,
  401. content,
  402. transformer,
  403. shouldCallTransform,
  404. options,
  405. processed,
  406. sourceMapPath
  407. ) {
  408. let transformed = {
  409. code: content,
  410. map: null
  411. };
  412. if (transformer && shouldCallTransform) {
  413. if (typeof processed === 'string') {
  414. transformed.code = processed;
  415. } else if (processed != null && typeof processed.code === 'string') {
  416. transformed = processed;
  417. } else {
  418. throw new Error(
  419. (0, _runtimeErrorsAndWarnings.makeInvalidReturnValueError)()
  420. );
  421. }
  422. }
  423. if (!transformed.map) {
  424. try {
  425. //Could be a potential freeze here.
  426. //See: https://github.com/facebook/jest/pull/5177#discussion_r158883570
  427. const inlineSourceMap = (0, _convertSourceMap().fromSource)(
  428. transformed.code
  429. );
  430. if (inlineSourceMap) {
  431. transformed.map = inlineSourceMap.toObject();
  432. }
  433. } catch {
  434. const transformPath = this._getTransformPath(filename);
  435. invariant(transformPath);
  436. console.warn(
  437. (0, _runtimeErrorsAndWarnings.makeInvalidSourceMapWarning)(
  438. filename,
  439. transformPath
  440. )
  441. );
  442. }
  443. } // That means that the transform has a custom instrumentation
  444. // logic and will handle it based on `config.collectCoverage` option
  445. const transformWillInstrument =
  446. shouldCallTransform && transformer && transformer.canInstrument; // Apply instrumentation to the code if necessary, keeping the instrumented code and new map
  447. let map = transformed.map;
  448. let code;
  449. if (!transformWillInstrument && options.instrument) {
  450. /**
  451. * We can map the original source code to the instrumented code ONLY if
  452. * - the process of transforming the code produced a source map e.g. ts-jest
  453. * - we did not transform the source code
  454. *
  455. * Otherwise we cannot make any statements about how the instrumented code corresponds to the original code,
  456. * and we should NOT emit any source maps
  457. *
  458. */
  459. const shouldEmitSourceMaps =
  460. (transformer != null && map != null) || transformer == null;
  461. const instrumented = this._instrumentFile(
  462. filename,
  463. transformed,
  464. shouldEmitSourceMaps,
  465. options
  466. );
  467. code =
  468. typeof instrumented === 'string' ? instrumented : instrumented.code;
  469. map = typeof instrumented === 'string' ? null : instrumented.map;
  470. } else {
  471. code = transformed.code;
  472. }
  473. if (map) {
  474. const sourceMapContent =
  475. typeof map === 'string' ? map : JSON.stringify(map);
  476. invariant(sourceMapPath, 'We should always have default sourceMapPath');
  477. writeCacheFile(sourceMapPath, sourceMapContent);
  478. } else {
  479. sourceMapPath = null;
  480. }
  481. writeCodeCacheFile(cacheFilePath, code);
  482. return {
  483. code,
  484. originalCode: content,
  485. sourceMapPath
  486. };
  487. }
  488. transformSource(filepath, content, options) {
  489. const filename = (0, _jestUtil().tryRealpath)(filepath);
  490. const {transformer, transformerConfig = {}} =
  491. this._getTransformer(filename) || {};
  492. const cacheFilePath = this._getFileCachePath(filename, content, options);
  493. const sourceMapPath = cacheFilePath + '.map'; // Ignore cache if `config.cache` is set (--no-cache)
  494. const code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null;
  495. if (code) {
  496. // This is broken: we return the code, and a path for the source map
  497. // directly from the cache. But, nothing ensures the source map actually
  498. // matches that source code. They could have gotten out-of-sync in case
  499. // two separate processes write concurrently to the same cache files.
  500. return {
  501. code,
  502. originalCode: content,
  503. sourceMapPath
  504. };
  505. }
  506. let processed = null;
  507. let shouldCallTransform = false;
  508. if (transformer && this.shouldTransform(filename)) {
  509. shouldCallTransform = true;
  510. assertSyncTransformer(transformer, this._getTransformPath(filename));
  511. processed = transformer.process(content, filename, {
  512. ...options,
  513. cacheFS: this._cacheFS,
  514. config: this._config,
  515. configString: this._cache.configString,
  516. transformerConfig
  517. });
  518. }
  519. return this._buildTransformResult(
  520. filename,
  521. cacheFilePath,
  522. content,
  523. transformer,
  524. shouldCallTransform,
  525. options,
  526. processed,
  527. sourceMapPath
  528. );
  529. }
  530. async transformSourceAsync(filepath, content, options) {
  531. const filename = (0, _jestUtil().tryRealpath)(filepath);
  532. const {transformer, transformerConfig = {}} =
  533. this._getTransformer(filename) || {};
  534. const cacheFilePath = await this._getFileCachePathAsync(
  535. filename,
  536. content,
  537. options
  538. );
  539. const sourceMapPath = cacheFilePath + '.map'; // Ignore cache if `config.cache` is set (--no-cache)
  540. const code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null;
  541. if (code) {
  542. // This is broken: we return the code, and a path for the source map
  543. // directly from the cache. But, nothing ensures the source map actually
  544. // matches that source code. They could have gotten out-of-sync in case
  545. // two separate processes write concurrently to the same cache files.
  546. return {
  547. code,
  548. originalCode: content,
  549. sourceMapPath
  550. };
  551. }
  552. let processed = null;
  553. let shouldCallTransform = false;
  554. if (transformer && this.shouldTransform(filename)) {
  555. shouldCallTransform = true;
  556. const process = transformer.processAsync || transformer.process; // This is probably dead code since `_getTransformerAsync` already asserts this
  557. invariant(
  558. typeof process === 'function',
  559. 'A transformer must always export either a `process` or `processAsync`'
  560. );
  561. processed = await process(content, filename, {
  562. ...options,
  563. cacheFS: this._cacheFS,
  564. config: this._config,
  565. configString: this._cache.configString,
  566. transformerConfig
  567. });
  568. }
  569. return this._buildTransformResult(
  570. filename,
  571. cacheFilePath,
  572. content,
  573. transformer,
  574. shouldCallTransform,
  575. options,
  576. processed,
  577. sourceMapPath
  578. );
  579. }
  580. async _transformAndBuildScriptAsync(
  581. filename,
  582. options,
  583. transformOptions,
  584. fileSource
  585. ) {
  586. const {isInternalModule} = options;
  587. let fileContent =
  588. fileSource !== null && fileSource !== void 0
  589. ? fileSource
  590. : this._cacheFS.get(filename);
  591. if (!fileContent) {
  592. fileContent = fs().readFileSync(filename, 'utf8');
  593. this._cacheFS.set(filename, fileContent);
  594. }
  595. const content = stripShebang(fileContent);
  596. let code = content;
  597. let sourceMapPath = null;
  598. const willTransform =
  599. !isInternalModule &&
  600. (transformOptions.instrument || this.shouldTransform(filename));
  601. try {
  602. if (willTransform) {
  603. const transformedSource = await this.transformSourceAsync(
  604. filename,
  605. content,
  606. transformOptions
  607. );
  608. code = transformedSource.code;
  609. sourceMapPath = transformedSource.sourceMapPath;
  610. }
  611. return {
  612. code,
  613. originalCode: content,
  614. sourceMapPath
  615. };
  616. } catch (e) {
  617. throw (0, _enhanceUnexpectedTokenMessage.default)(e);
  618. }
  619. }
  620. _transformAndBuildScript(filename, options, transformOptions, fileSource) {
  621. const {isInternalModule} = options;
  622. let fileContent =
  623. fileSource !== null && fileSource !== void 0
  624. ? fileSource
  625. : this._cacheFS.get(filename);
  626. if (!fileContent) {
  627. fileContent = fs().readFileSync(filename, 'utf8');
  628. this._cacheFS.set(filename, fileContent);
  629. }
  630. const content = stripShebang(fileContent);
  631. let code = content;
  632. let sourceMapPath = null;
  633. const willTransform =
  634. !isInternalModule &&
  635. (transformOptions.instrument || this.shouldTransform(filename));
  636. try {
  637. if (willTransform) {
  638. const transformedSource = this.transformSource(
  639. filename,
  640. content,
  641. transformOptions
  642. );
  643. code = transformedSource.code;
  644. sourceMapPath = transformedSource.sourceMapPath;
  645. }
  646. return {
  647. code,
  648. originalCode: content,
  649. sourceMapPath
  650. };
  651. } catch (e) {
  652. throw (0, _enhanceUnexpectedTokenMessage.default)(e);
  653. }
  654. }
  655. async transformAsync(filename, options, fileSource) {
  656. const instrument =
  657. options.coverageProvider === 'babel' &&
  658. (0, _shouldInstrument.default)(filename, options, this._config);
  659. const scriptCacheKey = getScriptCacheKey(filename, instrument);
  660. let result = this._cache.transformedFiles.get(scriptCacheKey);
  661. if (result) {
  662. return result;
  663. }
  664. result = await this._transformAndBuildScriptAsync(
  665. filename,
  666. options,
  667. {...options, instrument},
  668. fileSource
  669. );
  670. if (scriptCacheKey) {
  671. this._cache.transformedFiles.set(scriptCacheKey, result);
  672. }
  673. return result;
  674. }
  675. transform(filename, options, fileSource) {
  676. const instrument =
  677. options.coverageProvider === 'babel' &&
  678. (0, _shouldInstrument.default)(filename, options, this._config);
  679. const scriptCacheKey = getScriptCacheKey(filename, instrument);
  680. let result = this._cache.transformedFiles.get(scriptCacheKey);
  681. if (result) {
  682. return result;
  683. }
  684. result = this._transformAndBuildScript(
  685. filename,
  686. options,
  687. {...options, instrument},
  688. fileSource
  689. );
  690. if (scriptCacheKey) {
  691. this._cache.transformedFiles.set(scriptCacheKey, result);
  692. }
  693. return result;
  694. }
  695. transformJson(filename, options, fileSource) {
  696. const {isInternalModule} = options;
  697. const willTransform = !isInternalModule && this.shouldTransform(filename);
  698. if (willTransform) {
  699. const {code: transformedJsonSource} = this.transformSource(
  700. filename,
  701. fileSource,
  702. {...options, instrument: false}
  703. );
  704. return transformedJsonSource;
  705. }
  706. return fileSource;
  707. }
  708. async requireAndTranspileModule(
  709. moduleName,
  710. callback,
  711. options = {
  712. applyInteropRequireDefault: true,
  713. instrument: false,
  714. supportsDynamicImport: false,
  715. supportsExportNamespaceFrom: false,
  716. supportsStaticESM: false,
  717. supportsTopLevelAwait: false
  718. }
  719. ) {
  720. let transforming = false;
  721. const {applyInteropRequireDefault, ...transformOptions} = options;
  722. const revertHook = (0, _pirates().addHook)(
  723. (code, filename) => {
  724. try {
  725. transforming = true;
  726. return (
  727. this.transformSource(filename, code, transformOptions).code || code
  728. );
  729. } finally {
  730. transforming = false;
  731. }
  732. },
  733. {
  734. exts: this._config.moduleFileExtensions.map(ext => `.${ext}`),
  735. ignoreNodeModules: false,
  736. matcher: filename => {
  737. if (transforming) {
  738. // Don't transform any dependency required by the transformer itself
  739. return false;
  740. }
  741. return this.shouldTransform(filename);
  742. }
  743. }
  744. );
  745. try {
  746. const module = await (0, _jestUtil().requireOrImportModule)(
  747. moduleName,
  748. applyInteropRequireDefault
  749. );
  750. if (!callback) {
  751. revertHook();
  752. return module;
  753. }
  754. const cbResult = callback(module);
  755. if ((0, _jestUtil().isPromise)(cbResult)) {
  756. return waitForPromiseWithCleanup(cbResult, revertHook).then(
  757. () => module
  758. );
  759. }
  760. return module;
  761. } finally {
  762. revertHook();
  763. }
  764. }
  765. shouldTransform(filename) {
  766. const ignoreRegexp = this._cache.ignorePatternsRegExp;
  767. const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
  768. return this._config.transform.length !== 0 && !isIgnored;
  769. }
  770. } // TODO: do we need to define the generics twice?
  771. async function createTranspilingRequire(config) {
  772. const transformer = await createScriptTransformer(config);
  773. return async function requireAndTranspileModule(
  774. resolverPath,
  775. applyInteropRequireDefault = false
  776. ) {
  777. const transpiledModule = await transformer.requireAndTranspileModule(
  778. resolverPath,
  779. () => {},
  780. {
  781. applyInteropRequireDefault,
  782. instrument: false,
  783. supportsDynamicImport: false,
  784. // this might be true, depending on node version.
  785. supportsExportNamespaceFrom: false,
  786. supportsStaticESM: false,
  787. supportsTopLevelAwait: false
  788. }
  789. );
  790. return transpiledModule;
  791. };
  792. }
  793. const removeFile = path => {
  794. try {
  795. fs().unlinkSync(path);
  796. } catch {}
  797. };
  798. const stripShebang = content => {
  799. // If the file data starts with a shebang remove it. Leaves the empty line
  800. // to keep stack trace line numbers correct.
  801. if (content.startsWith('#!')) {
  802. return content.replace(/^#!.*/, '');
  803. } else {
  804. return content;
  805. }
  806. };
  807. /**
  808. * This is like `writeCacheFile` but with an additional sanity checksum. We
  809. * cannot use the same technique for source maps because we expose source map
  810. * cache file paths directly to callsites, with the expectation they can read
  811. * it right away. This is not a great system, because source map cache file
  812. * could get corrupted, out-of-sync, etc.
  813. */
  814. function writeCodeCacheFile(cachePath, code) {
  815. const checksum = (0, _crypto().createHash)('md5').update(code).digest('hex');
  816. writeCacheFile(cachePath, checksum + '\n' + code);
  817. }
  818. /**
  819. * Read counterpart of `writeCodeCacheFile`. We verify that the content of the
  820. * file matches the checksum, in case some kind of corruption happened. This
  821. * could happen if an older version of `jest-runtime` writes non-atomically to
  822. * the same cache, for example.
  823. */
  824. function readCodeCacheFile(cachePath) {
  825. const content = readCacheFile(cachePath);
  826. if (content == null) {
  827. return null;
  828. }
  829. const code = content.substring(33);
  830. const checksum = (0, _crypto().createHash)('md5').update(code).digest('hex');
  831. if (checksum === content.substring(0, 32)) {
  832. return code;
  833. }
  834. return null;
  835. }
  836. /**
  837. * Writing to the cache atomically relies on 'rename' being atomic on most
  838. * file systems. Doing atomic write reduces the risk of corruption by avoiding
  839. * two processes to write to the same file at the same time. It also reduces
  840. * the risk of reading a file that's being overwritten at the same time.
  841. */
  842. const writeCacheFile = (cachePath, fileData) => {
  843. try {
  844. (0, _writeFileAtomic().sync)(cachePath, fileData, {
  845. encoding: 'utf8',
  846. fsync: false
  847. });
  848. } catch (e) {
  849. if (cacheWriteErrorSafeToIgnore(e, cachePath)) {
  850. return;
  851. }
  852. e.message =
  853. 'jest: failed to cache transform results in: ' +
  854. cachePath +
  855. '\nFailure message: ' +
  856. e.message;
  857. removeFile(cachePath);
  858. throw e;
  859. }
  860. };
  861. /**
  862. * On Windows, renames are not atomic, leading to EPERM exceptions when two
  863. * processes attempt to rename to the same target file at the same time.
  864. * If the target file exists we can be reasonably sure another process has
  865. * legitimately won a cache write race and ignore the error.
  866. */
  867. const cacheWriteErrorSafeToIgnore = (e, cachePath) =>
  868. process.platform === 'win32' &&
  869. e.code === 'EPERM' &&
  870. fs().existsSync(cachePath);
  871. const readCacheFile = cachePath => {
  872. if (!fs().existsSync(cachePath)) {
  873. return null;
  874. }
  875. let fileData;
  876. try {
  877. fileData = fs().readFileSync(cachePath, 'utf8');
  878. } catch (e) {
  879. e.message =
  880. 'jest: failed to read cache file: ' +
  881. cachePath +
  882. '\nFailure message: ' +
  883. e.message;
  884. removeFile(cachePath);
  885. throw e;
  886. }
  887. if (fileData == null) {
  888. // We must have somehow created the file but failed to write to it,
  889. // let's delete it and retry.
  890. removeFile(cachePath);
  891. }
  892. return fileData;
  893. };
  894. const getScriptCacheKey = (filename, instrument) => {
  895. const mtime = fs().statSync(filename).mtime;
  896. return filename + '_' + mtime.getTime() + (instrument ? '_instrumented' : '');
  897. };
  898. const calcIgnorePatternRegExp = config => {
  899. if (
  900. !config.transformIgnorePatterns ||
  901. config.transformIgnorePatterns.length === 0
  902. ) {
  903. return undefined;
  904. }
  905. return new RegExp(config.transformIgnorePatterns.join('|'));
  906. };
  907. const calcTransformRegExp = config => {
  908. if (!config.transform.length) {
  909. return undefined;
  910. }
  911. const transformRegexp = [];
  912. for (let i = 0; i < config.transform.length; i++) {
  913. transformRegexp.push([
  914. new RegExp(config.transform[i][0]),
  915. config.transform[i][1],
  916. config.transform[i][2]
  917. ]);
  918. }
  919. return transformRegexp;
  920. };
  921. function invariant(condition, message) {
  922. if (!condition) {
  923. throw new Error(message);
  924. }
  925. }
  926. function assertSyncTransformer(transformer, name) {
  927. invariant(name);
  928. invariant(
  929. typeof transformer.process === 'function',
  930. (0, _runtimeErrorsAndWarnings.makeInvalidSyncTransformerError)(name)
  931. );
  932. }
  933. async function createScriptTransformer(config, cacheFS = new Map()) {
  934. const transformer = new ScriptTransformer(config, cacheFS);
  935. await transformer.loadTransformers();
  936. return transformer;
  937. }