-
-
Notifications
You must be signed in to change notification settings - Fork 6.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
jest-haste-map: watch-mode recover from duplicate modules #3107
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,7 +52,6 @@ type Options = { | |
resetCache?: boolean, | ||
retainAllFiles: boolean, | ||
roots: Array<string>, | ||
throwOnModuleCollision?: boolean, | ||
useWatchman?: boolean, | ||
watch?: boolean, | ||
}; | ||
|
@@ -70,7 +69,6 @@ type InternalOptions = { | |
resetCache: ?boolean, | ||
retainAllFiles: boolean, | ||
roots: Array<string>, | ||
throwOnModuleCollision: boolean, | ||
useWatchman: boolean, | ||
watch: boolean, | ||
}; | ||
|
@@ -215,7 +213,6 @@ class HasteMap extends EventEmitter { | |
resetCache: options.resetCache, | ||
retainAllFiles: options.retainAllFiles, | ||
roots: Array.from(new Set(options.roots)), | ||
throwOnModuleCollision: !!options.throwOnModuleCollision, | ||
useWatchman: options.useWatchman == null ? true : options.useWatchman, | ||
watch: !!options.watch, | ||
}; | ||
|
@@ -234,6 +231,8 @@ class HasteMap extends EventEmitter { | |
this._workerPromise = null; | ||
this._workerFarm = null; | ||
this._watchers = []; | ||
this._moduleIDsByFilePath = new Map(); | ||
this._moduleArraysByIDAndPlatform = new Map(); | ||
} | ||
|
||
static getCacheFilePath(tmpdir: Path, name: string): string { | ||
|
@@ -290,6 +289,9 @@ class HasteMap extends EventEmitter { | |
.then(hasteMap => this._crawl(hasteMap)); | ||
} | ||
|
||
_moduleIDsByFilePath: Map<string, string>; | ||
_moduleArraysByIDAndPlatform: Map<string, Map<string, Array<ModuleMetaData>>>; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you move these to the top of the file? |
||
|
||
/** | ||
* 3. parse and extract metadata from changed files. | ||
*/ | ||
|
@@ -299,30 +301,66 @@ class HasteMap extends EventEmitter { | |
mocks: Object, | ||
filePath: Path, | ||
workerOptions: ?{forceInBand: boolean}, | ||
): ?Promise<void> { | ||
const setModule = (id: string, module: ModuleMetaData) => { | ||
): ?Promise<?{id: string, candidates: Array<ModuleMetaData>}> { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please rm this empty line, prettier will remove it later on too :D |
||
const updateModuleFromCandidates = ( | ||
id: string, | ||
platform: string, | ||
candidates: Array<ModuleMetaData>, | ||
) => { | ||
if (!map[id]) { | ||
map[id] = Object.create(null); | ||
} | ||
const moduleMap = map[id]; | ||
const platform = getPlatformExtension(module[H.PATH]) || | ||
H.GENERIC_PLATFORM; | ||
const existingModule = moduleMap[platform]; | ||
if (existingModule && existingModule[H.PATH] !== module[H.PATH]) { | ||
const message = `jest-haste-map: @providesModule naming collision:\n` + | ||
` Duplicate module name: ${id}\n` + | ||
` Paths: ${module[H.PATH]} collides with ` + | ||
`${existingModule[H.PATH]}\n\nThis ` + | ||
`${this._options.throwOnModuleCollision ? 'error' : 'warning'} ` + | ||
`is caused by a @providesModule declaration ` + | ||
`with the same name across two different files.`; | ||
if (this._options.throwOnModuleCollision) { | ||
throw new Error(message); | ||
const modulesByPlatform = map[id]; | ||
if (candidates.length === 1) { | ||
modulesByPlatform[platform] = candidates[0]; | ||
} else { | ||
delete modulesByPlatform[platform]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it may make sense to add a comment here:
|
||
} | ||
return {id, candidates}; | ||
} | ||
|
||
const setModule = (id: string, module: ModuleMetaData) => { | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rm |
||
const moduleFilePath = module[H.PATH]; | ||
const platform = | ||
getPlatformExtension(moduleFilePath) || H.GENERIC_PLATFORM; | ||
|
||
const existingID = this._moduleIDsByFilePath.get(moduleFilePath); | ||
if (existingID != null) { | ||
const moduleArraysByPlatform = this._moduleArraysByIDAndPlatform.get(existingID); | ||
if (moduleArraysByPlatform == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
throw new Error('could not find data for existing ID'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are these just invariants that will never happen or do you expect user code to run into this for some reason? If it is user code, then please prefix them with |
||
} | ||
this._console.warn(message); | ||
const modules = moduleArraysByPlatform.get(platform); | ||
if (modules == null) { | ||
throw new Error('could not find candidate modules for existing ID and platform'); | ||
} | ||
const existingModuleIx = | ||
modules.findIndex(m => m[H.PATH] == moduleFilePath); | ||
if (existingModuleIx < 0) { | ||
throw new Error('could not find module for existing ID, platform and path'); | ||
} | ||
modules.splice(existingModuleIx, 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fancy. Why can we not use a Set for this? |
||
updateModuleFromCandidates(existingID, platform, modules); | ||
} | ||
|
||
this._moduleIDsByFilePath.set(moduleFilePath, id); | ||
|
||
let moduleArraysByPlatform = this._moduleArraysByIDAndPlatform.get(id); | ||
if (moduleArraysByPlatform == null) { | ||
moduleArraysByPlatform = new Map(); | ||
this._moduleArraysByIDAndPlatform.set(id, moduleArraysByPlatform); | ||
} | ||
|
||
moduleMap[platform] = module; | ||
let modules = moduleArraysByPlatform.get(platform); | ||
if (modules == null) { | ||
moduleArraysByPlatform.set(platform, modules = []); | ||
} | ||
|
||
modules.push(module); | ||
return updateModuleFromCandidates(id, platform, modules); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rm |
||
}; | ||
|
||
// If we retain all files in the virtual HasteFS representation, we avoid | ||
|
@@ -366,15 +404,17 @@ class HasteMap extends EventEmitter { | |
hasteImplModulePath: this._options.hasteImplModulePath, | ||
}).then( | ||
metadata => { | ||
let processingResult = null; | ||
// `1` for truthy values instead of `true` to save cache space. | ||
fileMetadata[H.VISITED] = 1; | ||
const metadataId = metadata.id; | ||
const metadataModule = metadata.module; | ||
if (metadataId && metadataModule) { | ||
fileMetadata[H.ID] = metadataId; | ||
setModule(metadataId, metadataModule); | ||
processingResult = setModule(metadataId, metadataModule); | ||
} | ||
fileMetadata[H.DEPENDENCIES] = metadata.dependencies || []; | ||
return processingResult; | ||
}, | ||
error => { | ||
// If a file cannot be read we remove it from the file list and | ||
|
@@ -391,9 +431,22 @@ class HasteMap extends EventEmitter { | |
|
||
for (const filePath in hasteMap.files) { | ||
const promise = this._processFile(hasteMap, map, mocks, filePath); | ||
if (promise) { | ||
promises.push(promise); | ||
if (!promise) { | ||
continue; | ||
} | ||
promises.push(promise.then(result => { | ||
if (result == null || result.candidates.length <= 1) { | ||
return; | ||
} | ||
throw new Error( | ||
`jest-haste-map: @providesModule naming collision:\n` + | ||
` Duplicate module name: ${result.id}\n` + | ||
` Paths: ${result.candidates[1][H.PATH]} collides with ` + | ||
`${result.candidates[0][H.PATH]}\n\nThis error ` + | ||
`is caused by a @providesModule declaration ` + | ||
`with the same name across several different files.`, | ||
); | ||
})); | ||
} | ||
|
||
const cleanup = () => { | ||
|
@@ -528,9 +581,7 @@ class HasteMap extends EventEmitter { | |
return Promise.resolve(); | ||
} | ||
|
||
// In watch mode, we'll only warn about module collisions and we'll retain | ||
// all files, even changes to node_modules. | ||
this._options.throwOnModuleCollision = false; | ||
// In watch mode, we'll retain all files, even changes to node_modules. | ||
this._options.retainAllFiles = true; | ||
|
||
const Watcher = canUseWatchman && this._options.useWatchman | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm being super annoying I'd ask you to sort these alphabetically within the block.