From 2e16c3a9ce51341ea28909f4840d62d797a8ae49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 3 Sep 2018 18:33:07 +0100 Subject: [PATCH 1/9] Implements a mapper option --- .../src/crawlers/__tests__/watchman.test.js | 28 +++++++++++++++++++ packages/jest-haste-map/src/crawlers/node.js | 4 +++ .../jest-haste-map/src/crawlers/watchman.js | 21 +++++++++++--- packages/jest-haste-map/src/types.js | 2 ++ 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js index 0db22b9abd21..0d39bea4d85a 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js @@ -42,6 +42,7 @@ const STRAWBERRY = path.join(FRUITS, 'strawberry.js'); const KIWI = path.join(FRUITS, 'kiwi.js'); const TOMATO = path.join(FRUITS, 'tomato.js'); const MELON = path.join(VEGETABLES, 'melon.json'); +const DURIAN = path.join(VEGETABLES, 'durian.zip'); const WATCH_PROJECT_MOCK = { [FRUITS]: { relative_path: 'fruits', @@ -89,6 +90,11 @@ describe('watchman watch', () => { mtime_ms: {toNumber: () => 33}, name: 'vegetables/melon.json', }, + { + exists: true, + mtime_ms: {toNumber: () => 33}, + name: 'vegetables/durian.zip', + }, ], is_fresh_instance: true, version: '4.5.0', @@ -157,6 +163,28 @@ describe('watchman watch', () => { expect(client.end).toBeCalled(); })); + test('applies the mapper when needed', async () => + watchmanCrawl({ + data: { + clocks: Object.create(null), + files: Object.create(null), + }, + extensions: ['js', 'json', 'zip'], + mapper: n => + n.endsWith('.zip') + ? [path.join(n, 'foo.1.js'), path.join(n, 'foo.2.js')] + : null, + ignore: pearMatcher, + roots: ROOTS, + }).then(data => { + expect(data.files).toEqual( + Object.assign({}, mockFiles, { + [path.join(DURIAN, 'foo.1.js')]: ['', 33, 0, [], null], + [path.join(DURIAN, 'foo.2.js')]: ['', 33, 0, [], null], + }), + ); + })); + test('updates the file object when the clock is given', () => { mockResponse = { 'list-capabilities': { diff --git a/packages/jest-haste-map/src/crawlers/node.js b/packages/jest-haste-map/src/crawlers/node.js index 96ce13d6e076..80957bd0508e 100644 --- a/packages/jest-haste-map/src/crawlers/node.js +++ b/packages/jest-haste-map/src/crawlers/node.js @@ -127,6 +127,10 @@ function findNative( module.exports = function nodeCrawl( options: CrawlerOptions, ): Promise { + if (options.mapper) { + throw new Error(`Option 'mapper' isn't supported on this crawler`); + } + const {data, extensions, forceNodeFilesystemAPI, ignore, roots} = options; return new Promise(resolve => { diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index 9e6af68d9a71..877882fd12cf 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -169,19 +169,32 @@ module.exports = async function watchmanCrawl( typeof fileData.mtime_ms === 'number' ? fileData.mtime_ms : fileData.mtime_ms.toNumber(); + const existingFileData = data.files[name]; + let nextData; + const isOld = existingFileData && existingFileData[H.MTIME] === mtime; if (isOld) { - files[name] = existingFileData; + nextData = existingFileData; } else { let sha1hex = fileData['content.sha1hex']; - if (typeof sha1hex !== 'string' || sha1hex.length !== 40) { sha1hex = null; } - // See ../constants.js - files[name] = ['', mtime, 0, [], sha1hex]; + nextData = ['', mtime, 0, [], sha1hex]; + } + + const mappings = options.mapper ? options.mapper(name) : nullo; + + if (mappings) { + for (const name of mappings) { + if (!ignore(name)) { + files[name] = nextData; + } + } + } else { + files[name] = nextData; } } } diff --git a/packages/jest-haste-map/src/types.js b/packages/jest-haste-map/src/types.js index 18db89daf8ba..1e17cd63e8ca 100644 --- a/packages/jest-haste-map/src/types.js +++ b/packages/jest-haste-map/src/types.js @@ -10,6 +10,7 @@ import type {InternalHasteMap, ModuleMetaData} from 'types/HasteMap'; export type IgnoreMatcher = (item: string) => boolean; +export type Mapper = (item: string) => ?Array; export type WorkerMessage = { computeDependencies: boolean, @@ -31,6 +32,7 @@ export type CrawlerOptions = {| extensions: Array, forceNodeFilesystemAPI: boolean, ignore: IgnoreMatcher, + mapper: ?Mapper, roots: Array, |}; From 29e3c17e20ce0bd8b31f589b0b3300b84b5c387f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 3 Sep 2018 18:35:55 +0100 Subject: [PATCH 2/9] Updates changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bedea88fdb91..1ad66583871f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Features - `[babel-jest]` Add support for `babel.config.js` added in Babel 7.0.0 ([#6911](https://github.com/facebook/jest/pull/6911)) +- `[jest-resolve]` Add support for an experimental `mapper` option (Watchman crawler only) that adds virtual files to the Haste map ([#6940](https://github.com/facebook/jest/pull/6940)) ### Fixes From 95557f53a82ed4206fde7a315a95c61d380d7975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 3 Sep 2018 18:39:47 +0100 Subject: [PATCH 3/9] Fixes linting --- packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js | 2 +- packages/jest-haste-map/src/crawlers/watchman.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js index 0d39bea4d85a..7518b269287b 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js @@ -170,11 +170,11 @@ describe('watchman watch', () => { files: Object.create(null), }, extensions: ['js', 'json', 'zip'], + ignore: pearMatcher, mapper: n => n.endsWith('.zip') ? [path.join(n, 'foo.1.js'), path.join(n, 'foo.2.js')] : null, - ignore: pearMatcher, roots: ROOTS, }).then(data => { expect(data.files).toEqual( diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index 877882fd12cf..f9d8b1db1af3 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -185,7 +185,7 @@ module.exports = async function watchmanCrawl( nextData = ['', mtime, 0, [], sha1hex]; } - const mappings = options.mapper ? options.mapper(name) : nullo; + const mappings = options.mapper ? options.mapper(name) : null; if (mappings) { for (const name of mappings) { From 8e7ba874191ee13a11caa947bb39beb18d4ae38a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 3 Sep 2018 18:52:04 +0100 Subject: [PATCH 4/9] Makes the mapper optional typewise --- packages/jest-haste-map/src/types.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-haste-map/src/types.js b/packages/jest-haste-map/src/types.js index 1e17cd63e8ca..f4a27d57de6c 100644 --- a/packages/jest-haste-map/src/types.js +++ b/packages/jest-haste-map/src/types.js @@ -32,7 +32,7 @@ export type CrawlerOptions = {| extensions: Array, forceNodeFilesystemAPI: boolean, ignore: IgnoreMatcher, - mapper: ?Mapper, + mapper?: ?Mapper, roots: Array, |}; From 0e334140af325ca483134da79fb8ed738ad6f3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 4 Sep 2018 12:50:04 +0100 Subject: [PATCH 5/9] Fixes tests --- .../src/crawlers/__tests__/watchman.test.js | 45 +++++++++++++------ 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js index 7518b269287b..d28462e4c515 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js @@ -90,11 +90,6 @@ describe('watchman watch', () => { mtime_ms: {toNumber: () => 33}, name: 'vegetables/melon.json', }, - { - exists: true, - mtime_ms: {toNumber: () => 33}, - name: 'vegetables/durian.zip', - }, ], is_fresh_instance: true, version: '4.5.0', @@ -163,8 +158,31 @@ describe('watchman watch', () => { expect(client.end).toBeCalled(); })); - test('applies the mapper when needed', async () => - watchmanCrawl({ + test('applies the mapper when needed', () => { + mockResponse = { + 'list-capabilities': { + [undefined]: { + capabilities: ['field-content.sha1hex'], + }, + }, + query: { + [ROOT_MOCK]: { + clock: 'c:fake-clock:1', + files: [ + { + exists: true, + mtime_ms: {toNumber: () => 33}, + name: 'vegetables/durian.zip', + }, + ], + is_fresh_instance: true, + version: '4.5.0', + }, + }, + 'watch-project': WATCH_PROJECT_MOCK, + }; + + return watchmanCrawl({ data: { clocks: Object.create(null), files: Object.create(null), @@ -177,13 +195,12 @@ describe('watchman watch', () => { : null, roots: ROOTS, }).then(data => { - expect(data.files).toEqual( - Object.assign({}, mockFiles, { - [path.join(DURIAN, 'foo.1.js')]: ['', 33, 0, [], null], - [path.join(DURIAN, 'foo.2.js')]: ['', 33, 0, [], null], - }), - ); - })); + expect(data.files).toEqual({ + [path.join(DURIAN, 'foo.1.js')]: ['', 33, 0, [], null], + [path.join(DURIAN, 'foo.2.js')]: ['', 33, 0, [], null], + }); + }); + }); test('updates the file object when the clock is given', () => { mockResponse = { From 5dc83c83db71e7f2be810e4c00ea172597c6dd87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 4 Sep 2018 13:00:01 +0100 Subject: [PATCH 6/9] Adds the plumbing to set the value from the options --- packages/jest-haste-map/src/index.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index 0d5bad0fcb44..db3090f59c10 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -32,6 +32,7 @@ import WatchmanWatcher from './lib/watchman_watcher'; import Worker from 'jest-worker'; import type {Console} from 'console'; +import type {Mapper} from './types'; import type {Path} from 'types/Config'; import type { HasteMap as HasteMapObject, @@ -53,6 +54,7 @@ type Options = { forceNodeFilesystemAPI?: boolean, hasteImplModulePath?: string, ignorePattern: HasteRegExp, + mapper?: ?Mapper, maxWorkers: number, mocksPattern?: string, name: string, @@ -74,6 +76,7 @@ type InternalOptions = { forceNodeFilesystemAPI: boolean, hasteImplModulePath?: string, ignorePattern: HasteRegExp, + mapper?: ?Mapper, maxWorkers: number, mocksPattern: ?RegExp, name: string, @@ -623,6 +626,7 @@ class HasteMap extends EventEmitter { extensions: options.extensions, forceNodeFilesystemAPI: options.forceNodeFilesystemAPI, ignore, + mapper: options.mapper, roots: options.roots, }).catch(e => { throw new Error( From 8a7b653928e2638c0f3fdecc5dc84fb1720ab3b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 2 Oct 2018 12:03:20 +0100 Subject: [PATCH 7/9] Updates the error message --- packages/jest-haste-map/src/crawlers/node.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/jest-haste-map/src/crawlers/node.js b/packages/jest-haste-map/src/crawlers/node.js index 80957bd0508e..2cefe7b3efa4 100644 --- a/packages/jest-haste-map/src/crawlers/node.js +++ b/packages/jest-haste-map/src/crawlers/node.js @@ -128,7 +128,7 @@ module.exports = function nodeCrawl( options: CrawlerOptions, ): Promise { if (options.mapper) { - throw new Error(`Option 'mapper' isn't supported on this crawler`); + throw new Error(`Option 'mapper' isn't supported by the Node crawler`); } const {data, extensions, forceNodeFilesystemAPI, ignore, roots} = options; From 60f37bf385f93f5f2373df657edf6a2a95543ea3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 2 Oct 2018 13:45:34 +0100 Subject: [PATCH 8/9] Linting --- packages/jest-haste-map/src/crawlers/watchman.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index 2f7c5334ad90..eb63a4f120ac 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -185,12 +185,13 @@ module.exports = async function watchmanCrawl( let nextData; const isOld = existingFileData && existingFileData[H.MTIME] === mtime; - const hasChanged = existingFileData && sha1hex && existingFileData[H.SHA1] === sha1hex; + const hasChanged = + existingFileData && sha1hex && existingFileData[H.SHA1] === sha1hex; if (existingFileData && isOld) { nextData = existingFileData; } else if (existingFileData && !hasChanged) { - nextData = [... existingFileData]; + nextData = [...existingFileData]; nextData[1] = mtime; } else { // See ../constants.js @@ -202,7 +203,10 @@ module.exports = async function watchmanCrawl( if (mappings) { for (const absoluteVirtualFilePath of mappings) { if (!ignore(absoluteVirtualFilePath)) { - const relativeVirtualFilePath = fastPath.relative(rootDir, filePath); + const relativeVirtualFilePath = fastPath.relative( + rootDir, + filePath, + ); files.set(relativeVirtualFilePath, nextData); } } From 4f2b9f4deac64e9f7755250adc1f6c6d7f704dbb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 2 Oct 2018 14:41:55 +0100 Subject: [PATCH 9/9] Fixes things --- .../src/crawlers/__tests__/watchman.test.js | 15 +++++++++------ packages/jest-haste-map/src/crawlers/watchman.js | 14 +++++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js index f06c27c9fa62..7f80e7dadf7e 100644 --- a/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js +++ b/packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js @@ -192,8 +192,8 @@ describe('watchman watch', () => { return watchmanCrawl({ data: { - clocks: Object.create(null), - files: Object.create(null), + clocks: new Map(), + files: new Map(), }, extensions: ['js', 'json', 'zip'], ignore: pearMatcher, @@ -201,12 +201,15 @@ describe('watchman watch', () => { n.endsWith('.zip') ? [path.join(n, 'foo.1.js'), path.join(n, 'foo.2.js')] : null, + rootDir: ROOT_MOCK, roots: ROOTS, }).then(data => { - expect(data.files).toEqual({ - [path.join(DURIAN_RELATIVE, 'foo.1.js')]: ['', 33, 0, [], null], - [path.join(DURIAN_RELATIVE, 'foo.2.js')]: ['', 33, 0, [], null], - }); + expect(data.files).toEqual( + createMap({ + [path.join(DURIAN_RELATIVE, 'foo.1.js')]: ['', 33, 0, [], null], + [path.join(DURIAN_RELATIVE, 'foo.2.js')]: ['', 33, 0, [], null], + }), + ); }); }); diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index eb63a4f120ac..d8fb38c5cf2f 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -184,13 +184,13 @@ module.exports = async function watchmanCrawl( const existingFileData = data.files.get(relativeFilePath); let nextData; - const isOld = existingFileData && existingFileData[H.MTIME] === mtime; - const hasChanged = - existingFileData && sha1hex && existingFileData[H.SHA1] === sha1hex; - - if (existingFileData && isOld) { + if (existingFileData && existingFileData[H.MTIME] === mtime) { nextData = existingFileData; - } else if (existingFileData && !hasChanged) { + } else if ( + existingFileData && + sha1hex && + existingFileData[H.SHA1] === sha1hex + ) { nextData = [...existingFileData]; nextData[1] = mtime; } else { @@ -205,7 +205,7 @@ module.exports = async function watchmanCrawl( if (!ignore(absoluteVirtualFilePath)) { const relativeVirtualFilePath = fastPath.relative( rootDir, - filePath, + absoluteVirtualFilePath, ); files.set(relativeVirtualFilePath, nextData); }