UNPKG

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