Skip to content

Commit d455d2d

Browse files
authored
[jest-haste-map] Use more optimal suffix-set format (#11784)
1 parent c8b8ce2 commit d455d2d

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44

5+
- `[jest-haste-map]` Use watchman suffix-set option for faster file indexing. ([#11784](https://github.com/facebook/jest/pull/11784))
56
- `[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))
67

78
### Fixes

packages/jest-haste-map/src/crawlers/__tests__/watchman.test.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ const path = require('path');
1313
jest.mock('fb-watchman', () => {
1414
const normalizePathSep = require('../../lib/normalizePathSep').default;
1515
const Client = jest.fn();
16+
Client.prototype.capabilityCheck = jest.fn((args, callback) =>
17+
setImmediate(() => {
18+
callback(null, {
19+
capabilities: {'suffix-set': true},
20+
version: '2021.06.07.00',
21+
});
22+
}),
23+
);
1624
Client.prototype.command = jest.fn((args, callback) =>
1725
setImmediate(() => {
1826
const path = args[1] ? normalizePathSep(args[1]) : undefined;
@@ -145,7 +153,7 @@ describe('watchman watch', () => {
145153
expect(query[2].expression).toEqual([
146154
'allof',
147155
['type', 'f'],
148-
['anyof', ['suffix', 'js'], ['suffix', 'json']],
156+
['suffix', ['js', 'json']],
149157
['anyof', ['dirname', 'fruits'], ['dirname', 'vegetables']],
150158
]);
151159

@@ -488,7 +496,7 @@ describe('watchman watch', () => {
488496
expect(query[2].expression).toEqual([
489497
'allof',
490498
['type', 'f'],
491-
['anyof', ['suffix', 'js'], ['suffix', 'json']],
499+
['suffix', ['js', 'json']],
492500
]);
493501

494502
expect(query[2].fields).toEqual(['name', 'exists', 'mtime_ms', 'size']);

packages/jest-haste-map/src/crawlers/watchman.ts

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ type WatchmanListCapabilitiesResponse = {
2424
capabilities: Array<string>;
2525
};
2626

27+
type WatchmanCapabilityCheckResponse = {
28+
// { 'suffix-set': true }
29+
capabilities: Record<string, boolean>;
30+
// '2021.06.07.00'
31+
version: string;
32+
};
33+
2734
type WatchmanWatchProjectResponse = {
2835
watch: string;
2936
relative_path: string;
@@ -57,21 +64,63 @@ function WatchmanError(error: Error): Error {
5764
return error;
5865
}
5966

67+
/**
68+
* Wrap watchman capabilityCheck method as a promise.
69+
*
70+
* @param client watchman client
71+
* @param caps capabilities to verify
72+
* @returns a promise resolving to a list of verified capabilities
73+
*/
74+
async function capabilityCheck(
75+
client: watchman.Client,
76+
caps: Partial<watchman.Capabilities>,
77+
): Promise<WatchmanCapabilityCheckResponse> {
78+
return new Promise((resolve, reject) => {
79+
client.capabilityCheck(
80+
// @ts-expect-error: incorrectly typed
81+
caps,
82+
(error, response) => {
83+
if (error) {
84+
reject(error);
85+
} else {
86+
resolve(response);
87+
}
88+
},
89+
);
90+
});
91+
}
92+
6093
export = async function watchmanCrawl(options: CrawlerOptions): Promise<{
6194
changedFiles?: FileData;
6295
removedFiles: FileData;
6396
hasteMap: InternalHasteMap;
6497
}> {
6598
const fields = ['name', 'exists', 'mtime_ms', 'size'];
6699
const {data, extensions, ignore, rootDir, roots} = options;
67-
const defaultWatchExpression = [
68-
'allof',
69-
['type', 'f'],
70-
['anyof', ...extensions.map(extension => ['suffix', extension])],
71-
];
100+
const defaultWatchExpression: Array<any> = ['allof', ['type', 'f']];
72101
const clocks = data.clocks;
73102
const client = new watchman.Client();
74103

104+
// https://facebook.github.io/watchman/docs/capabilities.html
105+
// Check adds about ~28ms
106+
const capabilities = await capabilityCheck(client, {
107+
// If a required capability is missing then an error will be thrown,
108+
// we don't need this assertion, so using optional instead.
109+
optional: ['suffix-set'],
110+
});
111+
112+
if (capabilities?.capabilities['suffix-set']) {
113+
// If available, use the optimized `suffix-set` operation:
114+
// https://facebook.github.io/watchman/docs/expr/suffix.html#suffix-set
115+
defaultWatchExpression.push(['suffix', extensions]);
116+
} else {
117+
// Otherwise use the older and less optimal suffix tuple array
118+
defaultWatchExpression.push([
119+
'anyof',
120+
...extensions.map(extension => ['suffix', extension]),
121+
]);
122+
}
123+
75124
let clientError;
76125
client.on('error', error => (clientError = WatchmanError(error)));
77126

0 commit comments

Comments
 (0)