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

[jest-haste-map] Use more optimal suffix-set format #11784

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

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

### Features

- `[jest-haste-map]` Use watchman suffix-set option for faster file indexing. ([#11784](https://github.com/facebook/jest/pull/11784))
- `[jest-cli]` Adds a new config options `snapshotFormat` which offers a way to override any of the formatting settings which come with [pretty-format](https://www.npmjs.com/package/pretty-format#usage-with-options). ([#11654](https://github.com/facebook/jest/pull/11654))

### Fixes
Expand Down
12 changes: 10 additions & 2 deletions packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js
Expand Up @@ -13,6 +13,14 @@ const path = require('path');
jest.mock('fb-watchman', () => {
const normalizePathSep = require('../../lib/normalizePathSep').default;
const Client = jest.fn();
Client.prototype.capabilityCheck = jest.fn((args, callback) =>
setImmediate(() => {
callback(null, {
capabilities: {'suffix-set': true},
version: '2021.06.07.00',
});
}),
);
Client.prototype.command = jest.fn((args, callback) =>
setImmediate(() => {
const path = args[1] ? normalizePathSep(args[1]) : undefined;
Expand Down Expand Up @@ -145,7 +153,7 @@ describe('watchman watch', () => {
expect(query[2].expression).toEqual([
'allof',
['type', 'f'],
['anyof', ['suffix', 'js'], ['suffix', 'json']],
['suffix', ['js', 'json']],
['anyof', ['dirname', 'fruits'], ['dirname', 'vegetables']],
]);

Expand Down Expand Up @@ -488,7 +496,7 @@ describe('watchman watch', () => {
expect(query[2].expression).toEqual([
'allof',
['type', 'f'],
['anyof', ['suffix', 'js'], ['suffix', 'json']],
['suffix', ['js', 'json']],
]);

expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms', 'size']);
Expand Down
59 changes: 54 additions & 5 deletions packages/jest-haste-map/src/crawlers/watchman.ts
Expand Up @@ -24,6 +24,13 @@ type WatchmanListCapabilitiesResponse = {
capabilities: Array<string>;
};

type WatchmanCapabilityCheckResponse = {
// { 'suffix-set': true }
capabilities: Record<string, boolean>;
// '2021.06.07.00'
version: string;
};

type WatchmanWatchProjectResponse = {
watch: string;
relative_path: string;
Expand Down Expand Up @@ -57,21 +64,63 @@ function WatchmanError(error: Error): Error {
return error;
}

/**
* Wrap watchman capabilityCheck method as a promise.
*
* @param client watchman client
* @param caps capabilities to verify
* @returns a promise resolving to a list of verified capabilities
*/
async function capabilityCheck(
client: watchman.Client,
caps: Partial<watchman.Capabilities>,
): Promise<WatchmanCapabilityCheckResponse> {
return new Promise((resolve, reject) => {
client.capabilityCheck(
// @ts-expect-error: incorrectly typed
caps,
(error, response) => {
if (error) {
reject(error);
} else {
resolve(response);
}
},
);
});
}

export = async function watchmanCrawl(options: CrawlerOptions): Promise<{
changedFiles?: FileData;
removedFiles: FileData;
hasteMap: InternalHasteMap;
}> {
const fields = ['name', 'exists', 'mtime_ms', 'size'];
const {data, extensions, ignore, rootDir, roots} = options;
const defaultWatchExpression = [
'allof',
['type', 'f'],
['anyof', ...extensions.map(extension => ['suffix', extension])],
];
const defaultWatchExpression: Array<any> = ['allof', ['type', 'f']];
const clocks = data.clocks;
const client = new watchman.Client();

// https://facebook.github.io/watchman/docs/capabilities.html
// Check adds about ~28ms
const capabilities = await capabilityCheck(client, {
// If a required capability is missing then an error will be thrown,
// we don't need this assertion, so using optional instead.
optional: ['suffix-set'],
});

if (capabilities?.capabilities['suffix-set']) {
// If available, use the optimized `suffix-set` operation:
// https://facebook.github.io/watchman/docs/expr/suffix.html#suffix-set
defaultWatchExpression.push(['suffix', extensions]);
} else {
// Otherwise use the older and less optimal suffix tuple array
defaultWatchExpression.push([
'anyof',
...extensions.map(extension => ['suffix', extension]),
]);
}

let clientError;
client.on('error', error => (clientError = WatchmanError(error)));

Expand Down