diff --git a/packages/jest-haste-map/src/crawlers/node.js b/packages/jest-haste-map/src/crawlers/node.js index 7f069afa51a1..8d8557457ec7 100644 --- a/packages/jest-haste-map/src/crawlers/node.js +++ b/packages/jest-haste-map/src/crawlers/node.js @@ -14,6 +14,7 @@ import fs from 'fs'; import path from 'path'; import {spawn} from 'child_process'; import H from '../constants'; +import * as fastPath from '../lib/fast_path'; type Callback = (result: Array<[/* id */ string, /* mtime */ number]>) => void; @@ -141,7 +142,7 @@ module.exports = function nodeCrawl( const files = new Map(); list.forEach(fileData => { const filePath = fileData[0]; - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); const mtime = fileData[1]; const existingFile = data.files.get(relativeFilePath); if (existingFile && existingFile[H.MTIME] === mtime) { diff --git a/packages/jest-haste-map/src/crawlers/watchman.js b/packages/jest-haste-map/src/crawlers/watchman.js index 17515c9f40cd..027017afed3e 100644 --- a/packages/jest-haste-map/src/crawlers/watchman.js +++ b/packages/jest-haste-map/src/crawlers/watchman.js @@ -10,6 +10,7 @@ import type {InternalHasteMap} from 'types/HasteMap'; import type {CrawlerOptions} from '../types'; +import * as fastPath from '../lib/fast_path'; import normalizePathSep from '../lib/normalize_path_sep'; import path from 'path'; import watchman from 'fb-watchman'; @@ -112,7 +113,7 @@ module.exports = async function watchmanCrawl( } } - const relativeRoot = path.relative(rootDir, root); + const relativeRoot = fastPath.relative(rootDir, root); const query = clocks.has(relativeRoot) ? // Use the `since` generator if we have a clock available {expression, fields, since: clocks.get(relativeRoot)} @@ -160,11 +161,12 @@ module.exports = async function watchmanCrawl( for (const [watchRoot, response] of watchmanFiles) { const fsRoot = normalizePathSep(watchRoot); - const relativeFsRoot = path.relative(rootDir, fsRoot); + const relativeFsRoot = fastPath.relative(rootDir, fsRoot); clocks.set(relativeFsRoot, response.clock); + for (const fileData of response.files) { const filePath = fsRoot + path.sep + normalizePathSep(fileData.name); - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); if (!fileData.exists) { files.delete(relativeFilePath); diff --git a/packages/jest-haste-map/src/haste_fs.js b/packages/jest-haste-map/src/haste_fs.js index 501fe2d66165..69df848a5975 100644 --- a/packages/jest-haste-map/src/haste_fs.js +++ b/packages/jest-haste-map/src/haste_fs.js @@ -10,7 +10,7 @@ import type {Glob, Path} from 'types/Config'; import type {FileData} from 'types/HasteMap'; -import path from 'path'; +import * as fastPath from './lib/fast_path'; import micromatch from 'micromatch'; import H from './constants'; @@ -48,7 +48,7 @@ export default class HasteFS { *getFileIterator(): Iterator { for (const file of this._files.keys()) { - yield path.resolve(this._rootDir, file); + yield fastPath.resolve(this._rootDir, file); } } @@ -68,7 +68,7 @@ export default class HasteFS { matchFilesWithGlob(globs: Array, root: ?Path): Set { const files = new Set(); for (const file of this.getFileIterator()) { - const filePath = root ? path.relative(root, file) : file; + const filePath = root ? fastPath.relative(root, file) : file; if (micromatch([filePath], globs).length) { files.add(file); } @@ -77,7 +77,7 @@ export default class HasteFS { } _getFileData(file: Path) { - const relativePath = path.relative(this._rootDir, file); + const relativePath = fastPath.relative(this._rootDir, file); return this._files.get(relativePath); } } diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index f045a2a47388..2cfc28a093ad 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -29,6 +29,7 @@ import serializer from 'jest-serializer'; // eslint-disable-next-line import/default import watchmanCrawl from './crawlers/watchman'; import WatchmanWatcher from './lib/watchman_watcher'; +import * as fastPath from './lib/fast_path'; import Worker from 'jest-worker'; import type {Console} from 'console'; @@ -259,7 +260,7 @@ class HasteMap extends EventEmitter { `haste-map-${this._options.name}`, VERSION, this._options.roots - .map(root => path.relative(options.rootDir, root)) + .map(root => fastPath.relative(options.rootDir, root)) .join(':'), this._options.extensions.join(':'), this._options.platforms.join(':'), @@ -395,8 +396,11 @@ class HasteMap extends EventEmitter { const message = `jest-haste-map: @providesModule naming collision:\n` + ` Duplicate module name: ${id}\n` + - ` Paths: ${path.resolve(rootDir, module[H.PATH])} collides with ` + - `${path.resolve(rootDir, existingModule[H.PATH])}\n\nThis ` + + ` Paths: ${fastPath.resolve( + rootDir, + module[H.PATH], + )} collides with ` + + `${fastPath.resolve(rootDir, existingModule[H.PATH])}\n\nThis ` + `${this._options.throwOnModuleCollision ? 'error' : 'warning'} ` + `is caused by a @providesModule declaration ` + `with the same name across two different files.`; @@ -432,7 +436,7 @@ class HasteMap extends EventEmitter { moduleMap[platform] = module; }; - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); const fileMetadata = hasteMap.files.get(relativeFilePath); if (!fileMetadata) { throw new Error( @@ -574,7 +578,10 @@ class HasteMap extends EventEmitter { for (const relativeFilePath of hasteMap.files.keys()) { // SHA-1, if requested, should already be present thanks to the crawler. - const filePath = path.resolve(this._options.rootDir, relativeFilePath); + const filePath = fastPath.resolve( + this._options.rootDir, + relativeFilePath, + ); const promise = this._processFile(hasteMap, map, mocks, filePath); if (promise) { promises.push(promise); @@ -802,7 +809,7 @@ class HasteMap extends EventEmitter { const add = () => eventsQueue.push({filePath, stat, type}); - const relativeFilePath = path.relative(rootDir, filePath); + const relativeFilePath = fastPath.relative(rootDir, filePath); const fileMetadata = hasteMap.files.get(relativeFilePath); // If it's not an addition, delete the file and all its metadata diff --git a/packages/jest-haste-map/src/lib/__tests__/fast_path.test.js b/packages/jest-haste-map/src/lib/__tests__/fast_path.test.js new file mode 100644 index 000000000000..d6872fec82de --- /dev/null +++ b/packages/jest-haste-map/src/lib/__tests__/fast_path.test.js @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +'use strict'; + +import path from 'path'; +import {relative, resolve} from '../fast_path'; + +describe('fastPath.relative', () => { + it('should get relative paths inside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const filename = path.join(__dirname, 'foo', 'bar', 'baz', 'foobar'); + const relativeFilename = path.join('baz', 'foobar'); + expect(relative(root, filename)).toBe(relativeFilename); + }); + + it('should get relative paths outside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const filename = path.join(__dirname, 'foo', 'baz', 'foobar'); + const relativeFilename = path.join('..', 'baz', 'foobar'); + expect(relative(root, filename)).toBe(relativeFilename); + }); +}); + +describe('fastPath.resolve', () => { + it('should get the absolute path for paths inside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const relativeFilename = path.join('baz', 'foobar'); + const filename = path.join(__dirname, 'foo', 'bar', 'baz', 'foobar'); + expect(resolve(root, relativeFilename)).toBe(filename); + }); + + it('should get the absolute path for paths outside the root', () => { + const root = path.join(__dirname, 'foo', 'bar'); + const relativeFilename = path.join('..', 'baz', 'foobar'); + const filename = path.join(__dirname, 'foo', 'baz', 'foobar'); + expect(resolve(root, relativeFilename)).toBe(filename); + }); +}); diff --git a/packages/jest-haste-map/src/lib/fast_path.js b/packages/jest-haste-map/src/lib/fast_path.js new file mode 100644 index 000000000000..57a46da418f2 --- /dev/null +++ b/packages/jest-haste-map/src/lib/fast_path.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. All rights reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import path from 'path'; + +export function relative(rootDir: string, filename: string): string { + return filename.indexOf(rootDir) === 0 + ? filename.substr(rootDir.length + 1) + : path.relative(rootDir, filename); +} + +const INDIRECTION_FRAGMENT = '..' + path.sep; + +export function resolve(rootDir: string, relativeFilename: string): string { + return relativeFilename.indexOf(INDIRECTION_FRAGMENT) === 0 + ? path.resolve(rootDir, relativeFilename) + : rootDir + path.sep + relativeFilename; +} diff --git a/packages/jest-haste-map/src/module_map.js b/packages/jest-haste-map/src/module_map.js index 6e15fd6b3373..a305d04c720a 100644 --- a/packages/jest-haste-map/src/module_map.js +++ b/packages/jest-haste-map/src/module_map.js @@ -18,7 +18,7 @@ import type { MockData, } from 'types/HasteMap'; -import path from 'path'; +import * as fastPath from './lib/fast_path'; import H from './constants'; const EMPTY_MAP = {}; @@ -58,7 +58,7 @@ export default class ModuleMap { ); if (module && module[H.TYPE] === type) { const modulePath = module[H.PATH]; - return modulePath && path.resolve(this._raw.rootDir, modulePath); + return modulePath && fastPath.resolve(this._raw.rootDir, modulePath); } return null; } @@ -74,7 +74,7 @@ export default class ModuleMap { getMockModule(name: string): ?Path { const mockPath = this._raw.mocks.get(name) || this._raw.mocks.get(name + '/index'); - return mockPath && path.resolve(this._raw.rootDir, mockPath); + return mockPath && fastPath.resolve(this._raw.rootDir, mockPath); } getRawModuleMap(): RawModuleMap { @@ -165,7 +165,7 @@ export default class ModuleMap { // Force flow refinement const previousSet: DuplicatesSet = relativePathSet; const set = Object.keys(previousSet).reduce((set, relativePath) => { - const duplicatePath = path.resolve(this._raw.rootDir, relativePath); + const duplicatePath = fastPath.resolve(this._raw.rootDir, relativePath); set[duplicatePath] = previousSet[relativePath]; return set; }, Object.create(null));