UNPKG

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