ScriptTransformer.js 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000
  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. /**
  140. * Copyright (c) Meta Platforms, Inc. and affiliates.
  141. *
  142. * This source code is licensed under the MIT license found in the
  143. * LICENSE file in the root directory of this source tree.
  144. */
  145. // @ts-expect-error: should just be `require.resolve`, but the tests mess that up
  146. // Use `require` to avoid TS rootDir
  147. const {version: VERSION} = require('../package.json');
  148. // This data structure is used to avoid recalculating some data every time that
  149. // we need to transform a file. Since ScriptTransformer is instantiated for each
  150. // file we need to keep this object in the local scope of this module.
  151. const projectCaches = new Map();
  152. // To reset the cache for specific changesets (rather than package version).
  153. const CACHE_VERSION = '1';
  154. async function waitForPromiseWithCleanup(promise, cleanup) {
  155. try {
  156. await promise;
  157. } finally {
  158. cleanup();
  159. }
  160. }
  161. // type predicate
  162. function isTransformerFactory(t) {
  163. return typeof t.createTransformer === 'function';
  164. }
  165. class ScriptTransformer {
  166. _cache;
  167. _transformCache = new Map();
  168. _transformsAreLoaded = false;
  169. constructor(_config, _cacheFS) {
  170. this._config = _config;
  171. this._cacheFS = _cacheFS;
  172. const configString = (0, _fastJsonStableStringify().default)(this._config);
  173. let projectCache = projectCaches.get(configString);
  174. if (!projectCache) {
  175. projectCache = {
  176. configString,
  177. ignorePatternsRegExp: calcIgnorePatternRegExp(this._config),
  178. transformRegExp: calcTransformRegExp(this._config),
  179. transformedFiles: new Map()
  180. };
  181. projectCaches.set(configString, projectCache);
  182. }
  183. this._cache = projectCache;
  184. }
  185. _buildCacheKeyFromFileInfo(
  186. fileData,
  187. filename,
  188. transformOptions,
  189. transformerCacheKey
  190. ) {
  191. if (transformerCacheKey != null) {
  192. return (0, _crypto().createHash)('sha1')
  193. .update(transformerCacheKey)
  194. .update(CACHE_VERSION)
  195. .digest('hex')
  196. .substring(0, 32);
  197. }
  198. return (0, _crypto().createHash)('sha1')
  199. .update(fileData)
  200. .update(transformOptions.configString)
  201. .update(transformOptions.instrument ? 'instrument' : '')
  202. .update(filename)
  203. .update(CACHE_VERSION)
  204. .digest('hex')
  205. .substring(0, 32);
  206. }
  207. _buildTransformCacheKey(pattern, filepath) {
  208. return pattern + filepath;
  209. }
  210. _getCacheKey(fileData, filename, options) {
  211. const configString = this._cache.configString;
  212. const {transformer, transformerConfig = {}} =
  213. this._getTransformer(filename) ?? {};
  214. let transformerCacheKey = undefined;
  215. const transformOptions = {
  216. ...options,
  217. cacheFS: this._cacheFS,
  218. config: this._config,
  219. configString,
  220. transformerConfig
  221. };
  222. if (typeof transformer?.getCacheKey === 'function') {
  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. _createCachedFilename(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.id}`,
  271. VERSION
  272. );
  273. // Create sub folders based on the cacheKey to avoid creating one
  274. // directory with many files.
  275. const cacheDir = path().join(baseCacheDir, cacheKey[0] + cacheKey[1]);
  276. const cacheFilenamePrefix = path()
  277. .basename(filename, path().extname(filename))
  278. .replace(/\W/g, '');
  279. return (0, _slash().default)(
  280. path().join(cacheDir, `${cacheFilenamePrefix}_${cacheKey}`)
  281. );
  282. }
  283. _getFileCachePath(filename, content, options) {
  284. const cacheKey = this._getCacheKey(content, filename, options);
  285. return this._createCachedFilename(filename, cacheKey);
  286. }
  287. async _getFileCachePathAsync(filename, content, options) {
  288. const cacheKey = await this._getCacheKeyAsync(content, filename, options);
  289. return this._createCachedFilename(filename, cacheKey);
  290. }
  291. _getTransformPatternAndPath(filename) {
  292. const transformEntry = this._cache.transformRegExp;
  293. if (transformEntry == null) {
  294. return undefined;
  295. }
  296. for (let i = 0; i < transformEntry.length; i++) {
  297. const [transformRegExp, transformPath] = transformEntry[i];
  298. if (transformRegExp.test(filename)) {
  299. return [transformRegExp.source, transformPath];
  300. }
  301. }
  302. return undefined;
  303. }
  304. _getTransformPath(filename) {
  305. const transformInfo = this._getTransformPatternAndPath(filename);
  306. if (!Array.isArray(transformInfo)) {
  307. return undefined;
  308. }
  309. return transformInfo[1];
  310. }
  311. async loadTransformers() {
  312. await Promise.all(
  313. this._config.transform.map(
  314. async ([transformPattern, transformPath, transformerConfig], i) => {
  315. let transformer = await (0, _jestUtil().requireOrImportModule)(
  316. transformPath
  317. );
  318. if (transformer == null) {
  319. throw new Error(
  320. (0, _runtimeErrorsAndWarnings.makeInvalidTransformerError)(
  321. transformPath
  322. )
  323. );
  324. }
  325. if (isTransformerFactory(transformer)) {
  326. transformer = await transformer.createTransformer(
  327. transformerConfig
  328. );
  329. }
  330. if (
  331. typeof transformer.process !== 'function' &&
  332. typeof transformer.processAsync !== 'function'
  333. ) {
  334. throw new Error(
  335. (0, _runtimeErrorsAndWarnings.makeInvalidTransformerError)(
  336. transformPath
  337. )
  338. );
  339. }
  340. const res = {
  341. transformer,
  342. transformerConfig
  343. };
  344. const transformCacheKey = this._buildTransformCacheKey(
  345. this._cache.transformRegExp?.[i]?.[0].source ??
  346. new RegExp(transformPattern).source,
  347. transformPath
  348. );
  349. this._transformCache.set(transformCacheKey, res);
  350. }
  351. )
  352. );
  353. this._transformsAreLoaded = true;
  354. }
  355. _getTransformer(filename) {
  356. if (!this._transformsAreLoaded) {
  357. throw new Error(
  358. 'Jest: Transformers have not been loaded yet - make sure to run `loadTransformers` and wait for it to complete before starting to transform files'
  359. );
  360. }
  361. if (this._config.transform.length === 0) {
  362. return null;
  363. }
  364. const transformPatternAndPath = this._getTransformPatternAndPath(filename);
  365. if (!Array.isArray(transformPatternAndPath)) {
  366. return null;
  367. }
  368. const [transformPattern, transformPath] = transformPatternAndPath;
  369. const transformCacheKey = this._buildTransformCacheKey(
  370. transformPattern,
  371. transformPath
  372. );
  373. const transformer = this._transformCache.get(transformCacheKey);
  374. if (transformer !== undefined) {
  375. return transformer;
  376. }
  377. throw new Error(
  378. `Jest was unable to load the transformer defined for ${filename}. This is a bug in Jest, please open up an issue`
  379. );
  380. }
  381. _instrumentFile(filename, input, canMapToInput, options) {
  382. const inputCode = typeof input === 'string' ? input : input.code;
  383. const inputMap = typeof input === 'string' ? null : input.map;
  384. const result = (0, _core().transformSync)(inputCode, {
  385. auxiliaryCommentBefore: ' istanbul ignore next ',
  386. babelrc: false,
  387. caller: {
  388. name: '@jest/transform',
  389. supportsDynamicImport: options.supportsDynamicImport,
  390. supportsExportNamespaceFrom: options.supportsExportNamespaceFrom,
  391. supportsStaticESM: options.supportsStaticESM,
  392. supportsTopLevelAwait: options.supportsTopLevelAwait
  393. },
  394. configFile: false,
  395. filename,
  396. plugins: [
  397. [
  398. _babelPluginIstanbul().default,
  399. {
  400. compact: false,
  401. // files outside `cwd` will not be instrumented
  402. cwd: this._config.rootDir,
  403. exclude: [],
  404. extension: false,
  405. inputSourceMap: inputMap,
  406. useInlineSourceMaps: false
  407. }
  408. ]
  409. ],
  410. sourceMaps: canMapToInput ? 'both' : false
  411. });
  412. if (result?.code != null) {
  413. return result;
  414. }
  415. return input;
  416. }
  417. _buildTransformResult(
  418. filename,
  419. cacheFilePath,
  420. content,
  421. transformer,
  422. shouldCallTransform,
  423. options,
  424. processed,
  425. sourceMapPath
  426. ) {
  427. let transformed = {
  428. code: content,
  429. map: null
  430. };
  431. if (transformer && shouldCallTransform) {
  432. if (processed != null && typeof processed.code === 'string') {
  433. transformed = processed;
  434. } else {
  435. const transformPath = this._getTransformPath(filename);
  436. (0, _jestUtil().invariant)(transformPath);
  437. throw new Error(
  438. (0, _runtimeErrorsAndWarnings.makeInvalidReturnValueError)(
  439. transformPath
  440. )
  441. );
  442. }
  443. }
  444. if (transformed.map == null || transformed.map === '') {
  445. try {
  446. //Could be a potential freeze here.
  447. //See: https://github.com/jestjs/jest/pull/5177#discussion_r158883570
  448. const inlineSourceMap = (0, _convertSourceMap().fromSource)(
  449. transformed.code
  450. );
  451. if (inlineSourceMap) {
  452. transformed.map = inlineSourceMap.toObject();
  453. }
  454. } catch {
  455. const transformPath = this._getTransformPath(filename);
  456. (0, _jestUtil().invariant)(transformPath);
  457. console.warn(
  458. (0, _runtimeErrorsAndWarnings.makeInvalidSourceMapWarning)(
  459. filename,
  460. transformPath
  461. )
  462. );
  463. }
  464. }
  465. // That means that the transform has a custom instrumentation
  466. // logic and will handle it based on `config.collectCoverage` option
  467. const transformWillInstrument =
  468. shouldCallTransform && transformer && transformer.canInstrument;
  469. // Apply instrumentation to the code if necessary, keeping the instrumented code and new map
  470. let map = transformed.map;
  471. let code;
  472. if (transformWillInstrument !== true && options.instrument) {
  473. /**
  474. * We can map the original source code to the instrumented code ONLY if
  475. * - the process of transforming the code produced a source map e.g. ts-jest
  476. * - we did not transform the source code
  477. *
  478. * Otherwise we cannot make any statements about how the instrumented code corresponds to the original code,
  479. * and we should NOT emit any source maps
  480. *
  481. */
  482. const shouldEmitSourceMaps =
  483. (transformer != null && map != null) || transformer == null;
  484. const instrumented = this._instrumentFile(
  485. filename,
  486. transformed,
  487. shouldEmitSourceMaps,
  488. options
  489. );
  490. code =
  491. typeof instrumented === 'string' ? instrumented : instrumented.code;
  492. map = typeof instrumented === 'string' ? null : instrumented.map;
  493. } else {
  494. code = transformed.code;
  495. }
  496. if (map != null) {
  497. const sourceMapContent =
  498. typeof map === 'string' ? map : JSON.stringify(map);
  499. (0, _jestUtil().invariant)(
  500. sourceMapPath,
  501. 'We should always have default sourceMapPath'
  502. );
  503. writeCacheFile(sourceMapPath, sourceMapContent);
  504. } else {
  505. sourceMapPath = null;
  506. }
  507. writeCodeCacheFile(cacheFilePath, code);
  508. return {
  509. code,
  510. originalCode: content,
  511. sourceMapPath
  512. };
  513. }
  514. transformSource(filepath, content, options) {
  515. const filename = (0, _jestUtil().tryRealpath)(filepath);
  516. const {transformer, transformerConfig = {}} =
  517. this._getTransformer(filename) ?? {};
  518. const cacheFilePath = this._getFileCachePath(filename, content, options);
  519. const sourceMapPath = `${cacheFilePath}.map`;
  520. // Ignore cache if `config.cache` is set (--no-cache)
  521. const code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null;
  522. if (code != null) {
  523. // This is broken: we return the code, and a path for the source map
  524. // directly from the cache. But, nothing ensures the source map actually
  525. // matches that source code. They could have gotten out-of-sync in case
  526. // two separate processes write concurrently to the same cache files.
  527. return {
  528. code,
  529. originalCode: content,
  530. sourceMapPath
  531. };
  532. }
  533. let processed = null;
  534. let shouldCallTransform = false;
  535. if (transformer && this.shouldTransform(filename)) {
  536. shouldCallTransform = true;
  537. assertSyncTransformer(transformer, this._getTransformPath(filename));
  538. processed = transformer.process(content, filename, {
  539. ...options,
  540. cacheFS: this._cacheFS,
  541. config: this._config,
  542. configString: this._cache.configString,
  543. transformerConfig
  544. });
  545. }
  546. (0, _jestUtil().createDirectory)(path().dirname(cacheFilePath));
  547. return this._buildTransformResult(
  548. filename,
  549. cacheFilePath,
  550. content,
  551. transformer,
  552. shouldCallTransform,
  553. options,
  554. processed,
  555. sourceMapPath
  556. );
  557. }
  558. async transformSourceAsync(filepath, content, options) {
  559. const filename = (0, _jestUtil().tryRealpath)(filepath);
  560. const {transformer, transformerConfig = {}} =
  561. this._getTransformer(filename) ?? {};
  562. const cacheFilePath = await this._getFileCachePathAsync(
  563. filename,
  564. content,
  565. options
  566. );
  567. const sourceMapPath = `${cacheFilePath}.map`;
  568. // Ignore cache if `config.cache` is set (--no-cache)
  569. const code = this._config.cache ? readCodeCacheFile(cacheFilePath) : null;
  570. if (code != null) {
  571. // This is broken: we return the code, and a path for the source map
  572. // directly from the cache. But, nothing ensures the source map actually
  573. // matches that source code. They could have gotten out-of-sync in case
  574. // two separate processes write concurrently to the same cache files.
  575. return {
  576. code,
  577. originalCode: content,
  578. sourceMapPath
  579. };
  580. }
  581. let processed = null;
  582. let shouldCallTransform = false;
  583. if (transformer && this.shouldTransform(filename)) {
  584. shouldCallTransform = true;
  585. const process = transformer.processAsync ?? transformer.process;
  586. // This is probably dead code since `_getTransformerAsync` already asserts this
  587. (0, _jestUtil().invariant)(
  588. typeof process === 'function',
  589. 'A transformer must always export either a `process` or `processAsync`'
  590. );
  591. processed = await process(content, filename, {
  592. ...options,
  593. cacheFS: this._cacheFS,
  594. config: this._config,
  595. configString: this._cache.configString,
  596. transformerConfig
  597. });
  598. }
  599. (0, _jestUtil().createDirectory)(path().dirname(cacheFilePath));
  600. return this._buildTransformResult(
  601. filename,
  602. cacheFilePath,
  603. content,
  604. transformer,
  605. shouldCallTransform,
  606. options,
  607. processed,
  608. sourceMapPath
  609. );
  610. }
  611. async _transformAndBuildScriptAsync(
  612. filename,
  613. options,
  614. transformOptions,
  615. fileSource
  616. ) {
  617. const {isInternalModule} = options;
  618. let fileContent = fileSource ?? this._cacheFS.get(filename);
  619. if (fileContent == null) {
  620. fileContent = fs().readFileSync(filename, 'utf8');
  621. this._cacheFS.set(filename, fileContent);
  622. }
  623. const content = stripShebang(fileContent);
  624. let code = content;
  625. let sourceMapPath = null;
  626. const willTransform =
  627. isInternalModule !== true &&
  628. (transformOptions.instrument || this.shouldTransform(filename));
  629. try {
  630. if (willTransform) {
  631. const transformedSource = await this.transformSourceAsync(
  632. filename,
  633. content,
  634. transformOptions
  635. );
  636. code = transformedSource.code;
  637. sourceMapPath = transformedSource.sourceMapPath;
  638. }
  639. return {
  640. code,
  641. originalCode: content,
  642. sourceMapPath
  643. };
  644. } catch (e) {
  645. if (!(e instanceof Error)) {
  646. throw e;
  647. }
  648. throw (0, _enhanceUnexpectedTokenMessage.default)(e);
  649. }
  650. }
  651. _transformAndBuildScript(filename, options, transformOptions, fileSource) {
  652. const {isInternalModule} = options;
  653. let fileContent = fileSource ?? this._cacheFS.get(filename);
  654. if (fileContent == null) {
  655. fileContent = fs().readFileSync(filename, 'utf8');
  656. this._cacheFS.set(filename, fileContent);
  657. }
  658. const content = stripShebang(fileContent);
  659. let code = content;
  660. let sourceMapPath = null;
  661. const willTransform =
  662. isInternalModule !== true &&
  663. (transformOptions.instrument || this.shouldTransform(filename));
  664. try {
  665. if (willTransform) {
  666. const transformedSource = this.transformSource(
  667. filename,
  668. content,
  669. transformOptions
  670. );
  671. code = transformedSource.code;
  672. sourceMapPath = transformedSource.sourceMapPath;
  673. }
  674. return {
  675. code,
  676. originalCode: content,
  677. sourceMapPath
  678. };
  679. } catch (e) {
  680. if (!(e instanceof Error)) {
  681. throw e;
  682. }
  683. throw (0, _enhanceUnexpectedTokenMessage.default)(e);
  684. }
  685. }
  686. async transformAsync(filename, options, fileSource) {
  687. const instrument =
  688. options.coverageProvider === 'babel' &&
  689. (0, _shouldInstrument.default)(filename, options, this._config);
  690. const scriptCacheKey = getScriptCacheKey(filename, instrument);
  691. let result = this._cache.transformedFiles.get(scriptCacheKey);
  692. if (result) {
  693. return result;
  694. }
  695. result = await this._transformAndBuildScriptAsync(
  696. filename,
  697. options,
  698. {
  699. ...options,
  700. instrument
  701. },
  702. fileSource
  703. );
  704. if (scriptCacheKey) {
  705. this._cache.transformedFiles.set(scriptCacheKey, result);
  706. }
  707. return result;
  708. }
  709. transform(filename, options, fileSource) {
  710. const instrument =
  711. options.coverageProvider === 'babel' &&
  712. (0, _shouldInstrument.default)(filename, options, this._config);
  713. const scriptCacheKey = getScriptCacheKey(filename, instrument);
  714. let result = this._cache.transformedFiles.get(scriptCacheKey);
  715. if (result) {
  716. return result;
  717. }
  718. result = this._transformAndBuildScript(
  719. filename,
  720. options,
  721. {
  722. ...options,
  723. instrument
  724. },
  725. fileSource
  726. );
  727. if (scriptCacheKey) {
  728. this._cache.transformedFiles.set(scriptCacheKey, result);
  729. }
  730. return result;
  731. }
  732. transformJson(filename, options, fileSource) {
  733. const {isInternalModule} = options;
  734. const willTransform =
  735. isInternalModule !== true && this.shouldTransform(filename);
  736. if (willTransform) {
  737. const {code: transformedJsonSource} = this.transformSource(
  738. filename,
  739. fileSource,
  740. {
  741. ...options,
  742. instrument: false
  743. }
  744. );
  745. return transformedJsonSource;
  746. }
  747. return fileSource;
  748. }
  749. async requireAndTranspileModule(
  750. moduleName,
  751. callback,
  752. options = {
  753. applyInteropRequireDefault: true,
  754. instrument: false,
  755. supportsDynamicImport: false,
  756. supportsExportNamespaceFrom: false,
  757. supportsStaticESM: false,
  758. supportsTopLevelAwait: false
  759. }
  760. ) {
  761. let transforming = false;
  762. const {applyInteropRequireDefault, ...transformOptions} = options;
  763. const revertHook = (0, _pirates().addHook)(
  764. (code, filename) => {
  765. try {
  766. transforming = true;
  767. return (
  768. this.transformSource(filename, code, transformOptions).code || code
  769. );
  770. } finally {
  771. transforming = false;
  772. }
  773. },
  774. {
  775. // Exclude `mjs` extension when addHook because pirates don't support hijack es module
  776. exts: this._config.moduleFileExtensions
  777. .filter(ext => ext !== 'mjs')
  778. .map(ext => `.${ext}`),
  779. ignoreNodeModules: false,
  780. matcher: filename => {
  781. if (transforming) {
  782. // Don't transform any dependency required by the transformer itself
  783. return false;
  784. }
  785. return this.shouldTransform(filename);
  786. }
  787. }
  788. );
  789. try {
  790. const module = await (0, _jestUtil().requireOrImportModule)(
  791. moduleName,
  792. applyInteropRequireDefault
  793. );
  794. if (!callback) {
  795. revertHook();
  796. return module;
  797. }
  798. const cbResult = callback(module);
  799. if ((0, _jestUtil().isPromise)(cbResult)) {
  800. return await waitForPromiseWithCleanup(cbResult, revertHook).then(
  801. () => module
  802. );
  803. }
  804. return module;
  805. } finally {
  806. revertHook();
  807. }
  808. }
  809. shouldTransform(filename) {
  810. const ignoreRegexp = this._cache.ignorePatternsRegExp;
  811. const isIgnored = ignoreRegexp ? ignoreRegexp.test(filename) : false;
  812. return this._config.transform.length !== 0 && !isIgnored;
  813. }
  814. }
  815. // TODO: do we need to define the generics twice?
  816. async function createTranspilingRequire(config) {
  817. const transformer = await createScriptTransformer(config);
  818. return async function requireAndTranspileModule(
  819. resolverPath,
  820. applyInteropRequireDefault = false
  821. ) {
  822. const transpiledModule = await transformer.requireAndTranspileModule(
  823. resolverPath,
  824. // eslint-disable-next-line @typescript-eslint/no-empty-function
  825. () => {},
  826. {
  827. applyInteropRequireDefault,
  828. instrument: false,
  829. supportsDynamicImport: false,
  830. // this might be true, depending on node version.
  831. supportsExportNamespaceFrom: false,
  832. supportsStaticESM: false,
  833. supportsTopLevelAwait: false
  834. }
  835. );
  836. return transpiledModule;
  837. };
  838. }
  839. const removeFile = path => {
  840. try {
  841. fs().unlinkSync(path);
  842. } catch {}
  843. };
  844. const stripShebang = content => {
  845. // If the file data starts with a shebang remove it. Leaves the empty line
  846. // to keep stack trace line numbers correct.
  847. if (content.startsWith('#!')) {
  848. return content.replace(/^#!.*/, '');
  849. } else {
  850. return content;
  851. }
  852. };
  853. /**
  854. * This is like `writeCacheFile` but with an additional sanity checksum. We
  855. * cannot use the same technique for source maps because we expose source map
  856. * cache file paths directly to callsites, with the expectation they can read
  857. * it right away. This is not a great system, because source map cache file
  858. * could get corrupted, out-of-sync, etc.
  859. */
  860. function writeCodeCacheFile(cachePath, code) {
  861. const checksum = (0, _crypto().createHash)('sha1')
  862. .update(code)
  863. .digest('hex')
  864. .substring(0, 32);
  865. writeCacheFile(cachePath, `${checksum}\n${code}`);
  866. }
  867. /**
  868. * Read counterpart of `writeCodeCacheFile`. We verify that the content of the
  869. * file matches the checksum, in case some kind of corruption happened. This
  870. * could happen if an older version of `jest-runtime` writes non-atomically to
  871. * the same cache, for example.
  872. */
  873. function readCodeCacheFile(cachePath) {
  874. const content = readCacheFile(cachePath);
  875. if (content == null) {
  876. return null;
  877. }
  878. const code = content.substring(33);
  879. const checksum = (0, _crypto().createHash)('sha1')
  880. .update(code)
  881. .digest('hex')
  882. .substring(0, 32);
  883. if (checksum === content.substring(0, 32)) {
  884. return code;
  885. }
  886. return null;
  887. }
  888. /**
  889. * Writing to the cache atomically relies on 'rename' being atomic on most
  890. * file systems. Doing atomic write reduces the risk of corruption by avoiding
  891. * two processes to write to the same file at the same time. It also reduces
  892. * the risk of reading a file that's being overwritten at the same time.
  893. */
  894. const writeCacheFile = (cachePath, fileData) => {
  895. try {
  896. (0, _writeFileAtomic().sync)(cachePath, fileData, {
  897. encoding: 'utf8',
  898. fsync: false
  899. });
  900. } catch (e) {
  901. if (!(e instanceof Error)) {
  902. throw e;
  903. }
  904. if (cacheWriteErrorSafeToIgnore(e, cachePath)) {
  905. return;
  906. }
  907. e.message = `jest: failed to cache transform results in: ${cachePath}\nFailure message: ${e.message}`;
  908. removeFile(cachePath);
  909. throw e;
  910. }
  911. };
  912. /**
  913. * On Windows, renames are not atomic, leading to EPERM exceptions when two
  914. * processes attempt to rename to the same target file at the same time.
  915. * If the target file exists we can be reasonably sure another process has
  916. * legitimately won a cache write race and ignore the error.
  917. */
  918. const cacheWriteErrorSafeToIgnore = (e, cachePath) =>
  919. process.platform === 'win32' &&
  920. e.code === 'EPERM' &&
  921. fs().existsSync(cachePath);
  922. const readCacheFile = cachePath => {
  923. if (!fs().existsSync(cachePath)) {
  924. return null;
  925. }
  926. let fileData;
  927. try {
  928. fileData = fs().readFileSync(cachePath, 'utf8');
  929. } catch (e) {
  930. if (!(e instanceof Error)) {
  931. throw e;
  932. }
  933. // on windows write-file-atomic is not atomic which can
  934. // result in this error
  935. if (e.code === 'ENOENT' && process.platform === 'win32') {
  936. return null;
  937. }
  938. e.message = `jest: failed to read cache file: ${cachePath}\nFailure message: ${e.message}`;
  939. removeFile(cachePath);
  940. throw e;
  941. }
  942. if (fileData == null) {
  943. // We must have somehow created the file but failed to write to it,
  944. // let's delete it and retry.
  945. removeFile(cachePath);
  946. }
  947. return fileData;
  948. };
  949. const getScriptCacheKey = (filename, instrument) => {
  950. const mtime = fs().statSync(filename).mtime;
  951. return `${filename}_${mtime.getTime()}${instrument ? '_instrumented' : ''}`;
  952. };
  953. const calcIgnorePatternRegExp = config => {
  954. if (
  955. config.transformIgnorePatterns == null ||
  956. config.transformIgnorePatterns.length === 0
  957. ) {
  958. return undefined;
  959. }
  960. return new RegExp(config.transformIgnorePatterns.join('|'));
  961. };
  962. const calcTransformRegExp = config => {
  963. if (!config.transform.length) {
  964. return undefined;
  965. }
  966. const transformRegexp = [];
  967. for (let i = 0; i < config.transform.length; i++) {
  968. transformRegexp.push([
  969. new RegExp(config.transform[i][0]),
  970. config.transform[i][1],
  971. config.transform[i][2]
  972. ]);
  973. }
  974. return transformRegexp;
  975. };
  976. function assertSyncTransformer(transformer, name) {
  977. (0, _jestUtil().invariant)(name);
  978. (0, _jestUtil().invariant)(
  979. typeof transformer.process === 'function',
  980. (0, _runtimeErrorsAndWarnings.makeInvalidSyncTransformerError)(name)
  981. );
  982. }
  983. async function createScriptTransformer(config, cacheFS = new Map()) {
  984. const transformer = new ScriptTransformer(config, cacheFS);
  985. await transformer.loadTransformers();
  986. return transformer;
  987. }