Skip to content
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

feat: symlinks in node_modules #6993

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- `[jest-jasmine2/jest-circus/jest-cli]` Add test.todo ([#6996](https://github.com/facebook/jest/pull/6996))
- `[pretty-format]` Option to not escape strings in diff messages ([#5661](https://github.com/facebook/jest/pull/5661))
- `[jest-haste-map]` Support for symlinks in `node_modules` ([#6993](https://github.com/facebook/jest/pull/6993))
- `[jest-haste-map]` Add `getFileIterator` to `HasteFS` for faster file iteration ([#7010](https://github.com/facebook/jest/pull/7010)).
- `[jest-worker]` [**BREAKING**] Add functionality to call a `setup` method in the worker before the first call and a `teardown` method when ending the farm ([#7014](https://github.com/facebook/jest/pull/7014)).
- `[jest-config]` [**BREAKING**] Set default `notifyMode` to `failure-change` ([#7024](https://github.com/facebook/jest/pull/7024))
Expand Down
1 change: 1 addition & 0 deletions packages/jest-haste-map/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"jest-serializer": "^23.0.1",
"jest-worker": "^23.2.0",
"micromatch": "^2.3.11",
"realpath-native": "^1.0.0",
"sane": "^3.0.0"
}
}
1 change: 1 addition & 0 deletions packages/jest-haste-map/src/crawlers/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ module.exports = function nodeCrawl(
}
});
data.files = files;
data.links = new Map();
resolve(data);
};

Expand Down
60 changes: 45 additions & 15 deletions packages/jest-haste-map/src/crawlers/watchman.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,21 @@ import H from '../constants';
const watchmanURL =
'https://facebook.github.io/watchman/docs/troubleshooting.html';

// Matches symlinks in "node_modules" directories.
const nodeModules = ['**/node_modules/*', '**/node_modules/@*/*'];
const linkExpression = [
'allof',
['type', 'l'],
['anyof'].concat(
nodeModules.map(glob => [
'match',
glob,
'wholename',
{includedotfiles: true},
]),
),
];

function WatchmanError(error: Error): Error {
error.message =
`Watchman error: ${error.message.trim()}. Make sure watchman ` +
Expand All @@ -29,9 +44,9 @@ function WatchmanError(error: Error): Error {
module.exports = async function watchmanCrawl(
options: CrawlerOptions,
): Promise<InternalHasteMap> {
const fields = ['name', 'exists', 'mtime_ms'];
const fields = ['name', 'type', 'exists', 'mtime_ms'];
const {data, extensions, ignore, rootDir, roots} = options;
const defaultWatchExpression = [
const fileExpression = [
'allof',
['type', 'f'],
['anyof'].concat(extensions.map(extension => ['suffix', extension])),
Expand Down Expand Up @@ -93,21 +108,24 @@ module.exports = async function watchmanCrawl(
await Promise.all(
Array.from(rootProjectDirMappings).map(
async ([root, directoryFilters]) => {
const expression = Array.from(defaultWatchExpression);
let expression = ['anyof', fileExpression, linkExpression];
const glob = [];

if (directoryFilters.length > 0) {
expression.push([
'anyof',
...directoryFilters.map(dir => ['dirname', dir]),
]);
expression = [
'allof',
['anyof'].concat(directoryFilters.map(dir => ['dirname', dir])),
expression,
];

for (const directory of directoryFilters) {
glob.push(...nodeModules.map(glob => directory + '/' + glob));
for (const extension of extensions) {
glob.push(`${directory}/**/*.${extension}`);
}
}
} else {
glob.push(...nodeModules);
for (const extension of extensions) {
glob.push(`**/*.${extension}`);
}
Expand All @@ -118,7 +136,7 @@ module.exports = async function watchmanCrawl(
? // Use the `since` generator if we have a clock available
{expression, fields, since: clocks.get(relativeRoot)}
: // Otherwise use the `glob` filter
{expression, fields, glob};
{expression, fields, glob, glob_includedotfiles: true};
aleclarson marked this conversation as resolved.
Show resolved Hide resolved

const response = await cmd('query', root, query);

Expand All @@ -139,6 +157,7 @@ module.exports = async function watchmanCrawl(
}

let files = data.files;
let links = data.links;
let watchmanFiles;
try {
const watchmanRoots = await getWatchmanRoots(roots);
Expand All @@ -148,6 +167,7 @@ module.exports = async function watchmanCrawl(
// files.
if (watchmanFileResults.isFresh) {
files = new Map();
links = new Map();
}

watchmanFiles = watchmanFileResults.files;
Expand All @@ -166,29 +186,38 @@ module.exports = async function watchmanCrawl(

for (const fileData of response.files) {
const filePath = fsRoot + path.sep + normalizePathSep(fileData.name);
const relativeFilePath = fastPath.relative(rootDir, filePath);
const fileName = fastPath.relative(rootDir, filePath);

const cache: Map<string, any> = fileData.type === 'f' ? files : links;
if (!fileData.exists) {
files.delete(relativeFilePath);
cache.delete(filePath);
} else if (!ignore(filePath)) {
const mtime =
typeof fileData.mtime_ms === 'number'
? fileData.mtime_ms
: fileData.mtime_ms.toNumber();

let sha1hex = fileData['content.sha1hex'];
if (typeof sha1hex !== 'string' || sha1hex.length !== 40) {
sha1hex = null;
}

const existingFileData = data.files.get(relativeFilePath);
const existingFileData: any =
fileData.type === 'f'
? data.files.get(fileName)
: data.links.get(fileName);

if (existingFileData && existingFileData[H.MTIME] === mtime) {
files.set(relativeFilePath, existingFileData);
cache.set(fileName, existingFileData);
} else if (fileData.type !== 'f') {
// See ../constants.js
cache.set(fileName, [undefined, mtime]);
} else if (
existingFileData &&
sha1hex &&
existingFileData &&
existingFileData[H.SHA1] === sha1hex
) {
files.set(relativeFilePath, [
cache.set(fileName, [
existingFileData[0],
mtime,
existingFileData[2],
Expand All @@ -197,12 +226,13 @@ module.exports = async function watchmanCrawl(
]);
} else {
// See ../constants.js
files.set(relativeFilePath, ['', mtime, 0, [], sha1hex]);
cache.set(fileName, ['', mtime, 0, [], sha1hex]);
}
}
}
}

data.files = files;
data.links = links;
return data;
};
26 changes: 24 additions & 2 deletions packages/jest-haste-map/src/haste_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,30 @@
*/

import type {Glob, Path} from 'types/Config';
import type {FileData} from 'types/HasteMap';
import type {FileData, LinkData} from 'types/HasteMap';

import * as fastPath from './lib/fast_path';
import micromatch from 'micromatch';
import {sync as realpath} from 'realpath-native';
import H from './constants';

export default class HasteFS {
_rootDir: Path;
_files: FileData;
_links: LinkData;

constructor({rootDir, files}: {rootDir: Path, files: FileData}) {
constructor({
rootDir,
files,
links,
}: {
rootDir: Path,
files: FileData,
links: LinkData,
}) {
this._rootDir = rootDir;
this._files = files;
this._links = links;
}

getModuleName(file: Path): ?string {
Expand All @@ -42,6 +53,17 @@ export default class HasteFS {
return this._getFileData(file) != null;
}

follow(file: Path): Path {
const link = this._links[file];
if (link === undefined) {
return file;
}
if (link[0] === undefined) {
link[0] = realpath(file);
}
return link[0];
}

getAllFiles(): Array<string> {
return Array.from(this.getFileIterator());
}
Expand Down
4 changes: 4 additions & 0 deletions packages/jest-haste-map/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ class HasteMap extends EventEmitter {
const rootDir = this._options.rootDir;
const hasteFS = new HasteFS({
files: hasteMap.files,
links: hasteMap.links,
rootDir,
});
const moduleMap = new HasteModuleMap({
Expand Down Expand Up @@ -760,6 +761,7 @@ class HasteMap extends EventEmitter {
eventsQueue,
hasteFS: new HasteFS({
files: hasteMap.files,
links: hasteMap.links,
rootDir,
}),
moduleMap: new HasteModuleMap({
Expand Down Expand Up @@ -811,6 +813,7 @@ class HasteMap extends EventEmitter {
clocks: new Map(hasteMap.clocks),
duplicates: new Map(hasteMap.duplicates),
files: new Map(hasteMap.files),
links: new Map(hasteMap.links),
map: new Map(hasteMap.map),
mocks: new Map(hasteMap.mocks),
};
Expand Down Expand Up @@ -1006,6 +1009,7 @@ class HasteMap extends EventEmitter {
clocks: new Map(),
duplicates: new Map(),
files: new Map(),
links: new Map(),
map: new Map(),
mocks: new Map(),
};
Expand Down
4 changes: 4 additions & 0 deletions types/HasteMap.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export type ModuleMap = _ModuleMap;
export type SerializableModuleMap = _SerializableModuleMap;

export type FileData = Map<Path, FileMetaData>;
export type LinkData = Map<Path, LinkMetaData>;
export type MockData = Map<string, Path>;
export type ModuleMapData = Map<string, ModuleMapItem>;
export type WatchmanClocks = Map<Path, string>;
Expand All @@ -37,6 +38,7 @@ export type InternalHasteMap = {|
clocks: WatchmanClocks,
duplicates: DuplicatesIndex,
files: FileData,
links: LinkData,
map: ModuleMapData,
mocks: MockData,
|};
Expand All @@ -62,6 +64,8 @@ export type FileMetaData = [
/* sha1 */ ?string,
];

export type LinkMetaData = [/* target */ ?string, /* mtime */ number];

type ModuleMapItem = {[platform: string]: ModuleMetaData, __proto__: null};
export type ModuleMetaData = [Path, /* type */ number];

Expand Down