index.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.default = exports.ModuleMap = exports.DuplicateError = void 0;
  6. function _crypto() {
  7. const data = require('crypto');
  8. _crypto = function () {
  9. return data;
  10. };
  11. return data;
  12. }
  13. function _events() {
  14. const data = require('events');
  15. _events = function () {
  16. return data;
  17. };
  18. return data;
  19. }
  20. function _os() {
  21. const data = require('os');
  22. _os = function () {
  23. return data;
  24. };
  25. return data;
  26. }
  27. function path() {
  28. const data = _interopRequireWildcard(require('path'));
  29. path = function () {
  30. return data;
  31. };
  32. return data;
  33. }
  34. function _v() {
  35. const data = require('v8');
  36. _v = function () {
  37. return data;
  38. };
  39. return data;
  40. }
  41. function _gracefulFs() {
  42. const data = require('graceful-fs');
  43. _gracefulFs = function () {
  44. return data;
  45. };
  46. return data;
  47. }
  48. function _jestRegexUtil() {
  49. const data = require('jest-regex-util');
  50. _jestRegexUtil = function () {
  51. return data;
  52. };
  53. return data;
  54. }
  55. function _jestUtil() {
  56. const data = require('jest-util');
  57. _jestUtil = function () {
  58. return data;
  59. };
  60. return data;
  61. }
  62. function _jestWorker() {
  63. const data = require('jest-worker');
  64. _jestWorker = function () {
  65. return data;
  66. };
  67. return data;
  68. }
  69. var _HasteFS = _interopRequireDefault(require('./HasteFS'));
  70. var _ModuleMap = _interopRequireDefault(require('./ModuleMap'));
  71. var _constants = _interopRequireDefault(require('./constants'));
  72. var _node = require('./crawlers/node');
  73. var _watchman = require('./crawlers/watchman');
  74. var _getMockName = _interopRequireDefault(require('./getMockName'));
  75. var fastPath = _interopRequireWildcard(require('./lib/fast_path'));
  76. var _getPlatformExtension = _interopRequireDefault(
  77. require('./lib/getPlatformExtension')
  78. );
  79. var _isWatchmanInstalled = _interopRequireDefault(
  80. require('./lib/isWatchmanInstalled')
  81. );
  82. var _normalizePathSep = _interopRequireDefault(
  83. require('./lib/normalizePathSep')
  84. );
  85. var _FSEventsWatcher = require('./watchers/FSEventsWatcher');
  86. var _NodeWatcher = _interopRequireDefault(require('./watchers/NodeWatcher'));
  87. var _WatchmanWatcher = _interopRequireDefault(
  88. require('./watchers/WatchmanWatcher')
  89. );
  90. var _worker = require('./worker');
  91. function _interopRequireDefault(obj) {
  92. return obj && obj.__esModule ? obj : {default: obj};
  93. }
  94. function _getRequireWildcardCache(nodeInterop) {
  95. if (typeof WeakMap !== 'function') return null;
  96. var cacheBabelInterop = new WeakMap();
  97. var cacheNodeInterop = new WeakMap();
  98. return (_getRequireWildcardCache = function (nodeInterop) {
  99. return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
  100. })(nodeInterop);
  101. }
  102. function _interopRequireWildcard(obj, nodeInterop) {
  103. if (!nodeInterop && obj && obj.__esModule) {
  104. return obj;
  105. }
  106. if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
  107. return {default: obj};
  108. }
  109. var cache = _getRequireWildcardCache(nodeInterop);
  110. if (cache && cache.has(obj)) {
  111. return cache.get(obj);
  112. }
  113. var newObj = {};
  114. var hasPropertyDescriptor =
  115. Object.defineProperty && Object.getOwnPropertyDescriptor;
  116. for (var key in obj) {
  117. if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
  118. var desc = hasPropertyDescriptor
  119. ? Object.getOwnPropertyDescriptor(obj, key)
  120. : null;
  121. if (desc && (desc.get || desc.set)) {
  122. Object.defineProperty(newObj, key, desc);
  123. } else {
  124. newObj[key] = obj[key];
  125. }
  126. }
  127. }
  128. newObj.default = obj;
  129. if (cache) {
  130. cache.set(obj, newObj);
  131. }
  132. return newObj;
  133. }
  134. /**
  135. * Copyright (c) Meta Platforms, Inc. and affiliates.
  136. *
  137. * This source code is licensed under the MIT license found in the
  138. * LICENSE file in the root directory of this source tree.
  139. */
  140. // @ts-expect-error: not converted to TypeScript - it's a fork: https://github.com/jestjs/jest/pull/10919
  141. // @ts-expect-error: not converted to TypeScript - it's a fork: https://github.com/jestjs/jest/pull/5387
  142. // TypeScript doesn't like us importing from outside `rootDir`, but it doesn't
  143. // understand `require`.
  144. const {version: VERSION} = require('../package.json');
  145. const ModuleMap = _ModuleMap.default;
  146. exports.ModuleMap = ModuleMap;
  147. const CHANGE_INTERVAL = 30;
  148. const MAX_WAIT_TIME = 240000;
  149. const NODE_MODULES = `${path().sep}node_modules${path().sep}`;
  150. const PACKAGE_JSON = `${path().sep}package.json`;
  151. const VCS_DIRECTORIES = ['.git', '.hg', '.sl']
  152. .map(vcs =>
  153. (0, _jestRegexUtil().escapePathForRegex)(path().sep + vcs + path().sep)
  154. )
  155. .join('|');
  156. /**
  157. * HasteMap is a JavaScript implementation of Facebook's haste module system.
  158. *
  159. * This implementation is inspired by https://github.com/facebook/node-haste
  160. * and was built with for high-performance in large code repositories with
  161. * hundreds of thousands of files. This implementation is scalable and provides
  162. * predictable performance.
  163. *
  164. * Because the haste map creation and synchronization is critical to startup
  165. * performance and most tasks are blocked by I/O this class makes heavy use of
  166. * synchronous operations. It uses worker processes for parallelizing file
  167. * access and metadata extraction.
  168. *
  169. * The data structures created by `jest-haste-map` can be used directly from the
  170. * cache without further processing. The metadata objects in the `files` and
  171. * `map` objects contain cross-references: a metadata object from one can look
  172. * up the corresponding metadata object in the other map. Note that in most
  173. * projects, the number of files will be greater than the number of haste
  174. * modules one module can refer to many files based on platform extensions.
  175. *
  176. * type HasteMap = {
  177. * clocks: WatchmanClocks,
  178. * files: {[filepath: string]: FileMetaData},
  179. * map: {[id: string]: ModuleMapItem},
  180. * mocks: {[id: string]: string},
  181. * }
  182. *
  183. * // Watchman clocks are used for query synchronization and file system deltas.
  184. * type WatchmanClocks = {[filepath: string]: string};
  185. *
  186. * type FileMetaData = {
  187. * id: ?string, // used to look up module metadata objects in `map`.
  188. * mtime: number, // check for outdated files.
  189. * size: number, // size of the file in bytes.
  190. * visited: boolean, // whether the file has been parsed or not.
  191. * dependencies: Array<string>, // all relative dependencies of this file.
  192. * sha1: ?string, // SHA-1 of the file, if requested via options.
  193. * };
  194. *
  195. * // Modules can be targeted to a specific platform based on the file name.
  196. * // Example: platform.ios.js and Platform.android.js will both map to the same
  197. * // `Platform` module. The platform should be specified during resolution.
  198. * type ModuleMapItem = {[platform: string]: ModuleMetaData};
  199. *
  200. * //
  201. * type ModuleMetaData = {
  202. * path: string, // the path to look up the file object in `files`.
  203. * type: string, // the module type (either `package` or `module`).
  204. * };
  205. *
  206. * Note that the data structures described above are conceptual only. The actual
  207. * implementation uses arrays and constant keys for metadata storage. Instead of
  208. * `{id: 'flatMap', mtime: 3421, size: 42, visited: true, dependencies: []}` the real
  209. * representation is similar to `['flatMap', 3421, 42, 1, []]` to save storage space
  210. * and reduce parse and write time of a big JSON blob.
  211. *
  212. * The HasteMap is created as follows:
  213. * 1. read data from the cache or create an empty structure.
  214. *
  215. * 2. crawl the file system.
  216. * * empty cache: crawl the entire file system.
  217. * * cache available:
  218. * * if watchman is available: get file system delta changes.
  219. * * if watchman is unavailable: crawl the entire file system.
  220. * * build metadata objects for every file. This builds the `files` part of
  221. * the `HasteMap`.
  222. *
  223. * 3. parse and extract metadata from changed files.
  224. * * this is done in parallel over worker processes to improve performance.
  225. * * the worst case is to parse all files.
  226. * * the best case is no file system access and retrieving all data from
  227. * the cache.
  228. * * the average case is a small number of changed files.
  229. *
  230. * 4. serialize the new `HasteMap` in a cache file.
  231. * Worker processes can directly access the cache through `HasteMap.read()`.
  232. *
  233. */
  234. class HasteMap extends _events().EventEmitter {
  235. _buildPromise = null;
  236. _cachePath = '';
  237. _changeInterval;
  238. _console;
  239. _isWatchmanInstalledPromise = null;
  240. _options;
  241. _watchers = [];
  242. _worker = null;
  243. static getStatic(config) {
  244. if (config.haste.hasteMapModulePath) {
  245. return require(config.haste.hasteMapModulePath);
  246. }
  247. return HasteMap;
  248. }
  249. static async create(options) {
  250. if (options.hasteMapModulePath) {
  251. const CustomHasteMap = require(options.hasteMapModulePath);
  252. return new CustomHasteMap(options);
  253. }
  254. const hasteMap = new HasteMap(options);
  255. await hasteMap.setupCachePath(options);
  256. return hasteMap;
  257. }
  258. constructor(options) {
  259. super();
  260. this._options = {
  261. cacheDirectory: options.cacheDirectory || (0, _os().tmpdir)(),
  262. computeDependencies: options.computeDependencies ?? true,
  263. computeSha1: options.computeSha1 || false,
  264. dependencyExtractor: options.dependencyExtractor || null,
  265. enableSymlinks: options.enableSymlinks || false,
  266. extensions: options.extensions,
  267. forceNodeFilesystemAPI: !!options.forceNodeFilesystemAPI,
  268. hasteImplModulePath: options.hasteImplModulePath,
  269. id: options.id,
  270. maxWorkers: options.maxWorkers,
  271. mocksPattern: options.mocksPattern
  272. ? new RegExp(options.mocksPattern)
  273. : null,
  274. platforms: options.platforms,
  275. resetCache: options.resetCache,
  276. retainAllFiles: options.retainAllFiles,
  277. rootDir: options.rootDir,
  278. roots: Array.from(new Set(options.roots)),
  279. skipPackageJson: !!options.skipPackageJson,
  280. throwOnModuleCollision: !!options.throwOnModuleCollision,
  281. useWatchman: options.useWatchman ?? true,
  282. watch: !!options.watch,
  283. workerThreads: options.workerThreads
  284. };
  285. this._console = options.console || globalThis.console;
  286. if (options.ignorePattern) {
  287. if (options.ignorePattern instanceof RegExp) {
  288. this._options.ignorePattern = new RegExp(
  289. options.ignorePattern.source.concat(`|${VCS_DIRECTORIES}`),
  290. options.ignorePattern.flags
  291. );
  292. } else {
  293. throw new Error(
  294. 'jest-haste-map: the `ignorePattern` option must be a RegExp'
  295. );
  296. }
  297. } else {
  298. this._options.ignorePattern = new RegExp(VCS_DIRECTORIES);
  299. }
  300. if (this._options.enableSymlinks && this._options.useWatchman) {
  301. throw new Error(
  302. 'jest-haste-map: enableSymlinks config option was set, but ' +
  303. 'is incompatible with watchman.\n' +
  304. 'Set either `enableSymlinks` to false or `useWatchman` to false.'
  305. );
  306. }
  307. }
  308. async setupCachePath(options) {
  309. const rootDirHash = (0, _crypto().createHash)('sha1')
  310. .update(options.rootDir)
  311. .digest('hex')
  312. .substring(0, 32);
  313. let hasteImplHash = '';
  314. let dependencyExtractorHash = '';
  315. if (options.hasteImplModulePath) {
  316. const hasteImpl = require(options.hasteImplModulePath);
  317. if (hasteImpl.getCacheKey) {
  318. hasteImplHash = String(hasteImpl.getCacheKey());
  319. }
  320. }
  321. if (options.dependencyExtractor) {
  322. const dependencyExtractor = await (0, _jestUtil().requireOrImportModule)(
  323. options.dependencyExtractor,
  324. false
  325. );
  326. if (dependencyExtractor.getCacheKey) {
  327. dependencyExtractorHash = String(dependencyExtractor.getCacheKey());
  328. }
  329. }
  330. this._cachePath = HasteMap.getCacheFilePath(
  331. this._options.cacheDirectory,
  332. `haste-map-${this._options.id}-${rootDirHash}`,
  333. VERSION,
  334. this._options.id,
  335. this._options.roots
  336. .map(root => fastPath.relative(options.rootDir, root))
  337. .join(':'),
  338. this._options.extensions.join(':'),
  339. this._options.platforms.join(':'),
  340. this._options.computeSha1.toString(),
  341. options.mocksPattern || '',
  342. (options.ignorePattern || '').toString(),
  343. hasteImplHash,
  344. dependencyExtractorHash,
  345. this._options.computeDependencies.toString()
  346. );
  347. }
  348. static getCacheFilePath(tmpdir, id, ...extra) {
  349. const hash = (0, _crypto().createHash)('sha1').update(extra.join(''));
  350. return path().join(
  351. tmpdir,
  352. `${id.replace(/\W/g, '-')}-${hash.digest('hex').substring(0, 32)}`
  353. );
  354. }
  355. static getModuleMapFromJSON(json) {
  356. return _ModuleMap.default.fromJSON(json);
  357. }
  358. getCacheFilePath() {
  359. return this._cachePath;
  360. }
  361. build() {
  362. if (!this._buildPromise) {
  363. this._buildPromise = (async () => {
  364. const data = await this._buildFileMap();
  365. // Persist when we don't know if files changed (changedFiles undefined)
  366. // or when we know a file was changed or deleted.
  367. let hasteMap;
  368. if (
  369. data.changedFiles === undefined ||
  370. data.changedFiles.size > 0 ||
  371. data.removedFiles.size > 0
  372. ) {
  373. hasteMap = await this._buildHasteMap(data);
  374. this._persist(hasteMap);
  375. } else {
  376. hasteMap = data.hasteMap;
  377. }
  378. const rootDir = this._options.rootDir;
  379. const hasteFS = new _HasteFS.default({
  380. files: hasteMap.files,
  381. rootDir
  382. });
  383. const moduleMap = new _ModuleMap.default({
  384. duplicates: hasteMap.duplicates,
  385. map: hasteMap.map,
  386. mocks: hasteMap.mocks,
  387. rootDir
  388. });
  389. const __hasteMapForTest =
  390. (process.env.NODE_ENV === 'test' && hasteMap) || null;
  391. await this._watch(hasteMap);
  392. return {
  393. __hasteMapForTest,
  394. hasteFS,
  395. moduleMap
  396. };
  397. })();
  398. }
  399. return this._buildPromise;
  400. }
  401. /**
  402. * 1. read data from the cache or create an empty structure.
  403. */
  404. read() {
  405. let hasteMap;
  406. try {
  407. hasteMap = (0, _v().deserialize)(
  408. (0, _gracefulFs().readFileSync)(this._cachePath)
  409. );
  410. } catch {
  411. hasteMap = this._createEmptyMap();
  412. }
  413. return hasteMap;
  414. }
  415. readModuleMap() {
  416. const data = this.read();
  417. return new _ModuleMap.default({
  418. duplicates: data.duplicates,
  419. map: data.map,
  420. mocks: data.mocks,
  421. rootDir: this._options.rootDir
  422. });
  423. }
  424. /**
  425. * 2. crawl the file system.
  426. */
  427. async _buildFileMap() {
  428. let hasteMap;
  429. try {
  430. const read = this._options.resetCache ? this._createEmptyMap : this.read;
  431. hasteMap = read.call(this);
  432. } catch {
  433. hasteMap = this._createEmptyMap();
  434. }
  435. return this._crawl(hasteMap);
  436. }
  437. /**
  438. * 3. parse and extract metadata from changed files.
  439. */
  440. _processFile(hasteMap, map, mocks, filePath, workerOptions) {
  441. const rootDir = this._options.rootDir;
  442. const setModule = (id, module) => {
  443. let moduleMap = map.get(id);
  444. if (!moduleMap) {
  445. moduleMap = Object.create(null);
  446. map.set(id, moduleMap);
  447. }
  448. const platform =
  449. (0, _getPlatformExtension.default)(
  450. module[_constants.default.PATH],
  451. this._options.platforms
  452. ) || _constants.default.GENERIC_PLATFORM;
  453. const existingModule = moduleMap[platform];
  454. if (
  455. existingModule &&
  456. existingModule[_constants.default.PATH] !==
  457. module[_constants.default.PATH]
  458. ) {
  459. const method = this._options.throwOnModuleCollision ? 'error' : 'warn';
  460. this._console[method](
  461. [
  462. `jest-haste-map: Haste module naming collision: ${id}`,
  463. ' The following files share their name; please adjust your hasteImpl:',
  464. ` * <rootDir>${path().sep}${
  465. existingModule[_constants.default.PATH]
  466. }`,
  467. ` * <rootDir>${path().sep}${module[_constants.default.PATH]}`,
  468. ''
  469. ].join('\n')
  470. );
  471. if (this._options.throwOnModuleCollision) {
  472. throw new DuplicateError(
  473. existingModule[_constants.default.PATH],
  474. module[_constants.default.PATH]
  475. );
  476. }
  477. // We do NOT want consumers to use a module that is ambiguous.
  478. delete moduleMap[platform];
  479. if (Object.keys(moduleMap).length === 1) {
  480. map.delete(id);
  481. }
  482. let dupsByPlatform = hasteMap.duplicates.get(id);
  483. if (dupsByPlatform == null) {
  484. dupsByPlatform = new Map();
  485. hasteMap.duplicates.set(id, dupsByPlatform);
  486. }
  487. const dups = new Map([
  488. [module[_constants.default.PATH], module[_constants.default.TYPE]],
  489. [
  490. existingModule[_constants.default.PATH],
  491. existingModule[_constants.default.TYPE]
  492. ]
  493. ]);
  494. dupsByPlatform.set(platform, dups);
  495. return;
  496. }
  497. const dupsByPlatform = hasteMap.duplicates.get(id);
  498. if (dupsByPlatform != null) {
  499. const dups = dupsByPlatform.get(platform);
  500. if (dups != null) {
  501. dups.set(
  502. module[_constants.default.PATH],
  503. module[_constants.default.TYPE]
  504. );
  505. }
  506. return;
  507. }
  508. moduleMap[platform] = module;
  509. };
  510. const relativeFilePath = fastPath.relative(rootDir, filePath);
  511. const fileMetadata = hasteMap.files.get(relativeFilePath);
  512. if (!fileMetadata) {
  513. throw new Error(
  514. 'jest-haste-map: File to process was not found in the haste map.'
  515. );
  516. }
  517. const moduleMetadata = hasteMap.map.get(
  518. fileMetadata[_constants.default.ID]
  519. );
  520. const computeSha1 =
  521. this._options.computeSha1 && !fileMetadata[_constants.default.SHA1];
  522. // Callback called when the response from the worker is successful.
  523. const workerReply = metadata => {
  524. // `1` for truthy values instead of `true` to save cache space.
  525. fileMetadata[_constants.default.VISITED] = 1;
  526. const metadataId = metadata.id;
  527. const metadataModule = metadata.module;
  528. if (metadataId && metadataModule) {
  529. fileMetadata[_constants.default.ID] = metadataId;
  530. setModule(metadataId, metadataModule);
  531. }
  532. fileMetadata[_constants.default.DEPENDENCIES] = metadata.dependencies
  533. ? metadata.dependencies.join(_constants.default.DEPENDENCY_DELIM)
  534. : '';
  535. if (computeSha1) {
  536. fileMetadata[_constants.default.SHA1] = metadata.sha1;
  537. }
  538. };
  539. // Callback called when the response from the worker is an error.
  540. const workerError = error => {
  541. if (typeof error !== 'object' || !error.message || !error.stack) {
  542. error = new Error(error);
  543. error.stack = ''; // Remove stack for stack-less errors.
  544. }
  545. if (!['ENOENT', 'EACCES'].includes(error.code)) {
  546. throw error;
  547. }
  548. // If a file cannot be read we remove it from the file list and
  549. // ignore the failure silently.
  550. hasteMap.files.delete(relativeFilePath);
  551. };
  552. // If we retain all files in the virtual HasteFS representation, we avoid
  553. // reading them if they aren't important (node_modules).
  554. if (this._options.retainAllFiles && filePath.includes(NODE_MODULES)) {
  555. if (computeSha1) {
  556. return this._getWorker(workerOptions)
  557. .getSha1({
  558. computeDependencies: this._options.computeDependencies,
  559. computeSha1,
  560. dependencyExtractor: this._options.dependencyExtractor,
  561. filePath,
  562. hasteImplModulePath: this._options.hasteImplModulePath,
  563. rootDir
  564. })
  565. .then(workerReply, workerError);
  566. }
  567. return null;
  568. }
  569. if (
  570. this._options.mocksPattern &&
  571. this._options.mocksPattern.test(filePath)
  572. ) {
  573. const mockPath = (0, _getMockName.default)(filePath);
  574. const existingMockPath = mocks.get(mockPath);
  575. if (existingMockPath) {
  576. const secondMockPath = fastPath.relative(rootDir, filePath);
  577. if (existingMockPath !== secondMockPath) {
  578. const method = this._options.throwOnModuleCollision
  579. ? 'error'
  580. : 'warn';
  581. this._console[method](
  582. [
  583. `jest-haste-map: duplicate manual mock found: ${mockPath}`,
  584. ' The following files share their name; please delete one of them:',
  585. ` * <rootDir>${path().sep}${existingMockPath}`,
  586. ` * <rootDir>${path().sep}${secondMockPath}`,
  587. ''
  588. ].join('\n')
  589. );
  590. if (this._options.throwOnModuleCollision) {
  591. throw new DuplicateError(existingMockPath, secondMockPath);
  592. }
  593. }
  594. }
  595. mocks.set(mockPath, relativeFilePath);
  596. }
  597. if (fileMetadata[_constants.default.VISITED]) {
  598. if (!fileMetadata[_constants.default.ID]) {
  599. return null;
  600. }
  601. if (moduleMetadata != null) {
  602. const platform =
  603. (0, _getPlatformExtension.default)(
  604. filePath,
  605. this._options.platforms
  606. ) || _constants.default.GENERIC_PLATFORM;
  607. const module = moduleMetadata[platform];
  608. if (module == null) {
  609. return null;
  610. }
  611. const moduleId = fileMetadata[_constants.default.ID];
  612. let modulesByPlatform = map.get(moduleId);
  613. if (!modulesByPlatform) {
  614. modulesByPlatform = Object.create(null);
  615. map.set(moduleId, modulesByPlatform);
  616. }
  617. modulesByPlatform[platform] = module;
  618. return null;
  619. }
  620. }
  621. return this._getWorker(workerOptions)
  622. .worker({
  623. computeDependencies: this._options.computeDependencies,
  624. computeSha1,
  625. dependencyExtractor: this._options.dependencyExtractor,
  626. filePath,
  627. hasteImplModulePath: this._options.hasteImplModulePath,
  628. rootDir
  629. })
  630. .then(workerReply, workerError);
  631. }
  632. _buildHasteMap(data) {
  633. const {removedFiles, changedFiles, hasteMap} = data;
  634. // If any files were removed or we did not track what files changed, process
  635. // every file looking for changes. Otherwise, process only changed files.
  636. let map;
  637. let mocks;
  638. let filesToProcess;
  639. if (changedFiles === undefined || removedFiles.size) {
  640. map = new Map();
  641. mocks = new Map();
  642. filesToProcess = hasteMap.files;
  643. } else {
  644. map = hasteMap.map;
  645. mocks = hasteMap.mocks;
  646. filesToProcess = changedFiles;
  647. }
  648. for (const [relativeFilePath, fileMetadata] of removedFiles) {
  649. this._recoverDuplicates(
  650. hasteMap,
  651. relativeFilePath,
  652. fileMetadata[_constants.default.ID]
  653. );
  654. }
  655. const promises = [];
  656. for (const relativeFilePath of filesToProcess.keys()) {
  657. if (
  658. this._options.skipPackageJson &&
  659. relativeFilePath.endsWith(PACKAGE_JSON)
  660. ) {
  661. continue;
  662. }
  663. // SHA-1, if requested, should already be present thanks to the crawler.
  664. const filePath = fastPath.resolve(
  665. this._options.rootDir,
  666. relativeFilePath
  667. );
  668. const promise = this._processFile(hasteMap, map, mocks, filePath);
  669. if (promise) {
  670. promises.push(promise);
  671. }
  672. }
  673. return Promise.all(promises).then(
  674. () => {
  675. this._cleanup();
  676. hasteMap.map = map;
  677. hasteMap.mocks = mocks;
  678. return hasteMap;
  679. },
  680. error => {
  681. this._cleanup();
  682. throw error;
  683. }
  684. );
  685. }
  686. _cleanup() {
  687. const worker = this._worker;
  688. if (worker && 'end' in worker) {
  689. worker.end();
  690. }
  691. this._worker = null;
  692. }
  693. /**
  694. * 4. serialize the new `HasteMap` in a cache file.
  695. */
  696. _persist(hasteMap) {
  697. (0, _gracefulFs().writeFileSync)(
  698. this._cachePath,
  699. (0, _v().serialize)(hasteMap)
  700. );
  701. }
  702. /**
  703. * Creates workers or parses files and extracts metadata in-process.
  704. */
  705. _getWorker(
  706. options = {
  707. forceInBand: false
  708. }
  709. ) {
  710. if (!this._worker) {
  711. if (options.forceInBand || this._options.maxWorkers <= 1) {
  712. this._worker = {
  713. getSha1: _worker.getSha1,
  714. worker: _worker.worker
  715. };
  716. } else {
  717. this._worker = new (_jestWorker().Worker)(require.resolve('./worker'), {
  718. enableWorkerThreads: this._options.workerThreads,
  719. exposedMethods: ['getSha1', 'worker'],
  720. forkOptions: {
  721. serialization: 'json'
  722. },
  723. maxRetries: 3,
  724. numWorkers: this._options.maxWorkers
  725. });
  726. }
  727. }
  728. return this._worker;
  729. }
  730. async _crawl(hasteMap) {
  731. const options = this._options;
  732. const ignore = this._ignore.bind(this);
  733. const crawl = (await this._shouldUseWatchman())
  734. ? _watchman.watchmanCrawl
  735. : _node.nodeCrawl;
  736. const crawlerOptions = {
  737. computeSha1: options.computeSha1,
  738. data: hasteMap,
  739. enableSymlinks: options.enableSymlinks,
  740. extensions: options.extensions,
  741. forceNodeFilesystemAPI: options.forceNodeFilesystemAPI,
  742. ignore,
  743. rootDir: options.rootDir,
  744. roots: options.roots
  745. };
  746. const retry = error => {
  747. if (crawl === _watchman.watchmanCrawl) {
  748. this._console.warn(
  749. 'jest-haste-map: Watchman crawl failed. Retrying once with node ' +
  750. 'crawler.\n' +
  751. " Usually this happens when watchman isn't running. Create an " +
  752. "empty `.watchmanconfig` file in your project's root folder or " +
  753. 'initialize a git or hg repository in your project.\n' +
  754. ` ${error}`
  755. );
  756. return (0, _node.nodeCrawl)(crawlerOptions).catch(e => {
  757. throw new Error(
  758. 'Crawler retry failed:\n' +
  759. ` Original error: ${error.message}\n` +
  760. ` Retry error: ${e.message}\n`
  761. );
  762. });
  763. }
  764. throw error;
  765. };
  766. try {
  767. return await crawl(crawlerOptions);
  768. } catch (error) {
  769. return retry(error);
  770. }
  771. }
  772. /**
  773. * Watch mode
  774. */
  775. async _watch(hasteMap) {
  776. if (!this._options.watch) {
  777. return Promise.resolve();
  778. }
  779. // In watch mode, we'll only warn about module collisions and we'll retain
  780. // all files, even changes to node_modules.
  781. this._options.throwOnModuleCollision = false;
  782. this._options.retainAllFiles = true;
  783. // WatchmanWatcher > FSEventsWatcher > sane.NodeWatcher
  784. const Watcher = (await this._shouldUseWatchman())
  785. ? _WatchmanWatcher.default
  786. : _FSEventsWatcher.FSEventsWatcher.isSupported()
  787. ? _FSEventsWatcher.FSEventsWatcher
  788. : _NodeWatcher.default;
  789. const extensions = this._options.extensions;
  790. const ignorePattern = this._options.ignorePattern;
  791. const rootDir = this._options.rootDir;
  792. let changeQueue = Promise.resolve();
  793. let eventsQueue = [];
  794. // We only need to copy the entire haste map once on every "frame".
  795. let mustCopy = true;
  796. const createWatcher = root => {
  797. const watcher = new Watcher(root, {
  798. dot: true,
  799. glob: extensions.map(extension => `**/*.${extension}`),
  800. ignored: ignorePattern
  801. });
  802. return new Promise((resolve, reject) => {
  803. const rejectTimeout = setTimeout(
  804. () => reject(new Error('Failed to start watch mode.')),
  805. MAX_WAIT_TIME
  806. );
  807. watcher.once('ready', () => {
  808. clearTimeout(rejectTimeout);
  809. watcher.on('all', onChange);
  810. resolve(watcher);
  811. });
  812. });
  813. };
  814. const emitChange = () => {
  815. if (eventsQueue.length) {
  816. mustCopy = true;
  817. const changeEvent = {
  818. eventsQueue,
  819. hasteFS: new _HasteFS.default({
  820. files: hasteMap.files,
  821. rootDir
  822. }),
  823. moduleMap: new _ModuleMap.default({
  824. duplicates: hasteMap.duplicates,
  825. map: hasteMap.map,
  826. mocks: hasteMap.mocks,
  827. rootDir
  828. })
  829. };
  830. this.emit('change', changeEvent);
  831. eventsQueue = [];
  832. }
  833. };
  834. const onChange = (type, filePath, root, stat) => {
  835. filePath = path().join(root, (0, _normalizePathSep.default)(filePath));
  836. if (
  837. (stat && stat.isDirectory()) ||
  838. this._ignore(filePath) ||
  839. !extensions.some(extension => filePath.endsWith(extension))
  840. ) {
  841. return;
  842. }
  843. const relativeFilePath = fastPath.relative(rootDir, filePath);
  844. const fileMetadata = hasteMap.files.get(relativeFilePath);
  845. // The file has been accessed, not modified
  846. if (
  847. type === 'change' &&
  848. fileMetadata &&
  849. stat &&
  850. fileMetadata[_constants.default.MTIME] === stat.mtime.getTime()
  851. ) {
  852. return;
  853. }
  854. changeQueue = changeQueue
  855. .then(() => {
  856. // If we get duplicate events for the same file, ignore them.
  857. if (
  858. eventsQueue.find(
  859. event =>
  860. event.type === type &&
  861. event.filePath === filePath &&
  862. ((!event.stat && !stat) ||
  863. (!!event.stat &&
  864. !!stat &&
  865. event.stat.mtime.getTime() === stat.mtime.getTime()))
  866. )
  867. ) {
  868. return null;
  869. }
  870. if (mustCopy) {
  871. mustCopy = false;
  872. hasteMap = {
  873. clocks: new Map(hasteMap.clocks),
  874. duplicates: new Map(hasteMap.duplicates),
  875. files: new Map(hasteMap.files),
  876. map: new Map(hasteMap.map),
  877. mocks: new Map(hasteMap.mocks)
  878. };
  879. }
  880. const add = () => {
  881. eventsQueue.push({
  882. filePath,
  883. stat,
  884. type
  885. });
  886. return null;
  887. };
  888. const fileMetadata = hasteMap.files.get(relativeFilePath);
  889. // If it's not an addition, delete the file and all its metadata
  890. if (fileMetadata != null) {
  891. const moduleName = fileMetadata[_constants.default.ID];
  892. const platform =
  893. (0, _getPlatformExtension.default)(
  894. filePath,
  895. this._options.platforms
  896. ) || _constants.default.GENERIC_PLATFORM;
  897. hasteMap.files.delete(relativeFilePath);
  898. let moduleMap = hasteMap.map.get(moduleName);
  899. if (moduleMap != null) {
  900. // We are forced to copy the object because jest-haste-map exposes
  901. // the map as an immutable entity.
  902. moduleMap = copy(moduleMap);
  903. delete moduleMap[platform];
  904. if (Object.keys(moduleMap).length === 0) {
  905. hasteMap.map.delete(moduleName);
  906. } else {
  907. hasteMap.map.set(moduleName, moduleMap);
  908. }
  909. }
  910. if (
  911. this._options.mocksPattern &&
  912. this._options.mocksPattern.test(filePath)
  913. ) {
  914. const mockName = (0, _getMockName.default)(filePath);
  915. hasteMap.mocks.delete(mockName);
  916. }
  917. this._recoverDuplicates(hasteMap, relativeFilePath, moduleName);
  918. }
  919. // If the file was added or changed,
  920. // parse it and update the haste map.
  921. if (type === 'add' || type === 'change') {
  922. (0, _jestUtil().invariant)(
  923. stat,
  924. 'since the file exists or changed, it should have stats'
  925. );
  926. const fileMetadata = [
  927. '',
  928. stat.mtime.getTime(),
  929. stat.size,
  930. 0,
  931. '',
  932. null
  933. ];
  934. hasteMap.files.set(relativeFilePath, fileMetadata);
  935. const promise = this._processFile(
  936. hasteMap,
  937. hasteMap.map,
  938. hasteMap.mocks,
  939. filePath,
  940. {
  941. forceInBand: true
  942. }
  943. );
  944. // Cleanup
  945. this._cleanup();
  946. if (promise) {
  947. return promise.then(add);
  948. } else {
  949. // If a file in node_modules has changed,
  950. // emit an event regardless.
  951. add();
  952. }
  953. } else {
  954. add();
  955. }
  956. return null;
  957. })
  958. .catch(error => {
  959. this._console.error(
  960. `jest-haste-map: watch error:\n ${error.stack}\n`
  961. );
  962. });
  963. };
  964. this._changeInterval = setInterval(emitChange, CHANGE_INTERVAL);
  965. return Promise.all(this._options.roots.map(createWatcher)).then(
  966. watchers => {
  967. this._watchers = watchers;
  968. }
  969. );
  970. }
  971. /**
  972. * This function should be called when the file under `filePath` is removed
  973. * or changed. When that happens, we want to figure out if that file was
  974. * part of a group of files that had the same ID. If it was, we want to
  975. * remove it from the group. Furthermore, if there is only one file
  976. * remaining in the group, then we want to restore that single file as the
  977. * correct resolution for its ID, and cleanup the duplicates index.
  978. */
  979. _recoverDuplicates(hasteMap, relativeFilePath, moduleName) {
  980. let dupsByPlatform = hasteMap.duplicates.get(moduleName);
  981. if (dupsByPlatform == null) {
  982. return;
  983. }
  984. const platform =
  985. (0, _getPlatformExtension.default)(
  986. relativeFilePath,
  987. this._options.platforms
  988. ) || _constants.default.GENERIC_PLATFORM;
  989. let dups = dupsByPlatform.get(platform);
  990. if (dups == null) {
  991. return;
  992. }
  993. dupsByPlatform = copyMap(dupsByPlatform);
  994. hasteMap.duplicates.set(moduleName, dupsByPlatform);
  995. dups = copyMap(dups);
  996. dupsByPlatform.set(platform, dups);
  997. dups.delete(relativeFilePath);
  998. if (dups.size !== 1) {
  999. return;
  1000. }
  1001. const uniqueModule = dups.entries().next().value;
  1002. if (!uniqueModule) {
  1003. return;
  1004. }
  1005. let dedupMap = hasteMap.map.get(moduleName);
  1006. if (!dedupMap) {
  1007. dedupMap = Object.create(null);
  1008. hasteMap.map.set(moduleName, dedupMap);
  1009. }
  1010. dedupMap[platform] = uniqueModule;
  1011. dupsByPlatform.delete(platform);
  1012. if (dupsByPlatform.size === 0) {
  1013. hasteMap.duplicates.delete(moduleName);
  1014. }
  1015. }
  1016. async end() {
  1017. if (this._changeInterval) {
  1018. clearInterval(this._changeInterval);
  1019. }
  1020. if (!this._watchers.length) {
  1021. return;
  1022. }
  1023. await Promise.all(this._watchers.map(watcher => watcher.close()));
  1024. this._watchers = [];
  1025. }
  1026. /**
  1027. * Helpers
  1028. */
  1029. _ignore(filePath) {
  1030. const ignorePattern = this._options.ignorePattern;
  1031. const ignoreMatched =
  1032. ignorePattern instanceof RegExp
  1033. ? ignorePattern.test(filePath)
  1034. : ignorePattern && ignorePattern(filePath);
  1035. return (
  1036. ignoreMatched ||
  1037. (!this._options.retainAllFiles && filePath.includes(NODE_MODULES))
  1038. );
  1039. }
  1040. async _shouldUseWatchman() {
  1041. if (!this._options.useWatchman) {
  1042. return false;
  1043. }
  1044. if (!this._isWatchmanInstalledPromise) {
  1045. this._isWatchmanInstalledPromise = (0, _isWatchmanInstalled.default)();
  1046. }
  1047. return this._isWatchmanInstalledPromise;
  1048. }
  1049. _createEmptyMap() {
  1050. return {
  1051. clocks: new Map(),
  1052. duplicates: new Map(),
  1053. files: new Map(),
  1054. map: new Map(),
  1055. mocks: new Map()
  1056. };
  1057. }
  1058. static H = _constants.default;
  1059. }
  1060. class DuplicateError extends Error {
  1061. mockPath1;
  1062. mockPath2;
  1063. constructor(mockPath1, mockPath2) {
  1064. super('Duplicated files or mocks. Please check the console for more info');
  1065. this.mockPath1 = mockPath1;
  1066. this.mockPath2 = mockPath2;
  1067. }
  1068. }
  1069. exports.DuplicateError = DuplicateError;
  1070. function copy(object) {
  1071. return Object.assign(Object.create(null), object);
  1072. }
  1073. function copyMap(input) {
  1074. return new Map(input);
  1075. }
  1076. // Export the smallest API surface required by Jest
  1077. const JestHasteMap = HasteMap;
  1078. var _default = JestHasteMap;
  1079. exports.default = _default;