Skip to content

Commit

Permalink
Reduce duplication, tighten types in Watcher backends, remove anymatc…
Browse files Browse the repository at this point in the history
…h dependency

Differential Revision: D67259981
  • Loading branch information
robhogan authored and facebook-github-bot committed Dec 16, 2024
1 parent 250e344 commit e43b5da
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 61 deletions.
1 change: 0 additions & 1 deletion packages/metro-file-map/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
},
"license": "MIT",
"dependencies": {
"anymatch": "^3.0.3",
"debug": "^2.2.0",
"fb-watchman": "^2.0.0",
"flow-enums-runtime": "^0.0.6",
Expand Down
66 changes: 21 additions & 45 deletions packages/metro-file-map/src/watchers/FSEventsWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,22 @@
*/

import type {ChangeEventMetadata} from '../flow-types';
import type {Stats} from 'fs';
// $FlowFixMe[cannot-resolve-module] - Optional, Darwin only
// $FlowFixMe[untyped-type-import]
import type {FSEvents} from 'fsevents';

import {isIncluded, typeFromStat} from './common';
// $FlowFixMe[untyped-import] - anymatch
import anymatch from 'anymatch';
import {
isIncluded,
posixPathMatchesPattern,
recReaddir,
typeFromStat,
} from './common';
import EventEmitter from 'events';
import {promises as fsPromises} from 'fs';
import * as path from 'path';
// $FlowFixMe[untyped-import] - walker
import walker from 'walker';

const debug = require('debug')('Metro:FSEventsWatcher');

type Matcher = typeof anymatch.Matcher;

// $FlowFixMe[value-as-type]
let fsevents: ?FSEvents = null;
try {
Expand Down Expand Up @@ -54,7 +52,7 @@ type FsEventsWatcherEvent =
*/
export default class FSEventsWatcher extends EventEmitter {
+root: string;
+ignored: ?Matcher;
+ignored: ?RegExp;
+glob: $ReadOnlyArray<string>;
+dot: boolean;
+doIgnore: (path: string) => boolean;
Expand All @@ -66,38 +64,14 @@ export default class FSEventsWatcher extends EventEmitter {
return fsevents != null;
}

static _normalizeProxy(
callback: (normalizedPath: string, stats: Stats) => void,
): (filepath: string, stats: Stats) => void {
return (filepath: string, stats: Stats): void =>
callback(path.normalize(filepath), stats);
}

static _recReaddir(
dir: string,
dirCallback: (normalizedPath: string, stats: Stats) => void,
fileCallback: (normalizedPath: string, stats: Stats) => void,
symlinkCallback: (normalizedPath: string, stats: Stats) => void,
endCallback: () => void,
// $FlowFixMe[unclear-type] Add types for callback
errorCallback: Function,
ignored?: Matcher,
) {
walker(dir)
.filterDir(
(currentDir: string) => !ignored || !anymatch(ignored, currentDir),
)
.on('dir', FSEventsWatcher._normalizeProxy(dirCallback))
.on('file', FSEventsWatcher._normalizeProxy(fileCallback))
.on('symlink', FSEventsWatcher._normalizeProxy(symlinkCallback))
.on('error', errorCallback)
.on('end', endCallback);
}

constructor(
dir: string,
opts: $ReadOnly<{
ignored?: Matcher,
{
ignored,
glob,
dot,
}: $ReadOnly<{
ignored?: ?RegExp,
glob: string | $ReadOnlyArray<string>,
dot: boolean,
...
Expand All @@ -111,10 +85,12 @@ export default class FSEventsWatcher extends EventEmitter {

super();

this.dot = opts.dot || false;
this.ignored = opts.ignored;
this.glob = Array.isArray(opts.glob) ? opts.glob : [opts.glob];
this.doIgnore = opts.ignored ? anymatch(opts.ignored) : () => false;
this.dot = dot || false;
this.ignored = ignored;
this.glob = Array.isArray(glob) ? glob : [glob];
this.doIgnore = ignored
? (filePath: string) => posixPathMatchesPattern(ignored, filePath)
: () => false;

this.root = path.resolve(dir);

Expand All @@ -128,10 +104,10 @@ export default class FSEventsWatcher extends EventEmitter {

this._tracked = new Set();
const trackPath = (filePath: string) => {
this._tracked.add(filePath);
this._tracked.add(path.normalize(filePath));
};
this.watcherInitialReaddirPromise = new Promise(resolve => {
FSEventsWatcher._recReaddir(
recReaddir(
this.root,
trackPath,
trackPath,
Expand Down
2 changes: 1 addition & 1 deletion packages/metro-file-map/src/watchers/NodeWatcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ module.exports = class NodeWatcher extends EventEmitter {
doIgnore: string => boolean;
dot: boolean;
globs: $ReadOnlyArray<string>;
ignored: ?(boolean | RegExp);
ignored: ?RegExp;
root: string;
watched: {[key: string]: FSWatcher, __proto__: null};
watchmanDeferStates: $ReadOnlyArray<string>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe('WatchmanWatcher', () => {
test('initializes with watch-project, clock, subscribe', () => {
const watchmanWatcher = new WatchmanWatcher('/project/subdir/js', {
dot: true,
ignored: false,
ignored: null,
glob: ['**/*.js'],
watchmanDeferStates: ['busy'],
});
Expand Down Expand Up @@ -86,7 +86,7 @@ describe('WatchmanWatcher', () => {
beforeEach(() => {
watchmanWatcher = new WatchmanWatcher('/project/subdir/js', {
dot: true,
ignored: false,
ignored: null,
glob: ['**/*.js'],
watchmanDeferStates: ['busy'],
});
Expand Down
47 changes: 35 additions & 12 deletions packages/metro-file-map/src/watchers/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@
import type {ChangeEventMetadata} from '../flow-types';
import type {Stats} from 'fs';

// $FlowFixMe[untyped-import] - Write libdefs for `anymatch`
const anymatch = require('anymatch');
// $FlowFixMe[untyped-import] - Write libdefs for `micromatch`
const micromatch = require('micromatch');
const platform = require('os').platform();
Expand All @@ -39,7 +37,7 @@ export const ALL_EVENT = 'all';
export type WatcherOptions = $ReadOnly<{
glob: $ReadOnlyArray<string>,
dot: boolean,
ignored: boolean | RegExp,
ignored: ?RegExp,
watchmanDeferStates: $ReadOnlyArray<string>,
watchman?: mixed,
watchmanPath?: string,
Expand All @@ -49,7 +47,7 @@ interface Watcher {
doIgnore: string => boolean;
dot: boolean;
globs: $ReadOnlyArray<string>;
ignored?: ?(boolean | RegExp);
ignored?: ?RegExp;
watchmanDeferStates: $ReadOnlyArray<string>;
watchmanPath?: ?string;
}
Expand All @@ -68,16 +66,16 @@ export const assignOptions = function (
): WatcherOptions {
watcher.globs = opts.glob ?? [];
watcher.dot = opts.dot ?? false;
watcher.ignored = opts.ignored ?? false;
watcher.ignored = opts.ignored ?? null;
watcher.watchmanDeferStates = opts.watchmanDeferStates;

if (!Array.isArray(watcher.globs)) {
watcher.globs = [watcher.globs];
}
watcher.doIgnore =
opts.ignored != null && opts.ignored !== false
? anymatch(opts.ignored)
: () => false;
const ignored = watcher.ignored;
watcher.doIgnore = ignored
? filePath => ignored.test(toPosixSeparators(filePath))
: () => false;

if (opts.watchman == true && opts.watchmanPath != null) {
watcher.watchmanPath = opts.watchmanPath;
Expand Down Expand Up @@ -107,6 +105,26 @@ export function isIncluded(
return micromatch.some(relativePath, globs, {dot});
}

const toPosixSeparators: (filePath: string) => string =
path.sep === '/'
? filePath => filePath
: filePath => filePath.replaceAll(path.sep, '/');

/**
* Whether the given filePath matches the given RegExp, after converting
* (on Windows only) system separators to posix separators.
*
* Conversion to posix is for backwards compatibility with the previous
* anymatch matcher, which normlises all inputs[1]. This may not be consistent
* with other parts of metro-file-map.
*
* [1]: https://github.com/micromatch/anymatch/blob/3.1.1/index.js#L50
*/
export const posixPathMatchesPattern = (
pattern: RegExp,
filePath: string,
): boolean => pattern.test(toPosixSeparators(filePath));

/**
* Traverse a directory recursively calling `callback` on every directory.
*/
Expand All @@ -117,10 +135,15 @@ export function recReaddir(
symlinkCallback: (string, Stats) => void,
endCallback: () => void,
errorCallback: Error => void,
ignored: ?(boolean | RegExp),
ignored: ?RegExp,
) {
walker(dir)
.filterDir(currentDir => !anymatch(ignored, currentDir))
const walk = walker(dir);
if (ignored) {
walk.filterDir(
(currentDir: string) => !posixPathMatchesPattern(ignored, currentDir),
);
}
walk
.on('dir', normalizeProxy(dirCallback))
.on('file', normalizeProxy(fileCallback))
.on('symlink', normalizeProxy(symlinkCallback))
Expand Down

0 comments on commit e43b5da

Please sign in to comment.