aboutsummaryrefslogtreecommitdiff
path: root/node_modules/jest-haste-map/build/watchers/NodeWatcher.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/jest-haste-map/build/watchers/NodeWatcher.js')
-rw-r--r--node_modules/jest-haste-map/build/watchers/NodeWatcher.js375
1 files changed, 375 insertions, 0 deletions
diff --git a/node_modules/jest-haste-map/build/watchers/NodeWatcher.js b/node_modules/jest-haste-map/build/watchers/NodeWatcher.js
new file mode 100644
index 0000000..8ae1bc4
--- /dev/null
+++ b/node_modules/jest-haste-map/build/watchers/NodeWatcher.js
@@ -0,0 +1,375 @@
+// vendored from https://github.com/amasad/sane/blob/64ff3a870c42e84f744086884bf55a4f9c22d376/src/node_watcher.js
+'use strict';
+
+const EventEmitter = require('events').EventEmitter;
+
+const fs = require('fs');
+
+const platform = require('os').platform();
+
+const path = require('path');
+
+const common = require('./common');
+/**
+ * Constants
+ */
+
+const DEFAULT_DELAY = common.DEFAULT_DELAY;
+const CHANGE_EVENT = common.CHANGE_EVENT;
+const DELETE_EVENT = common.DELETE_EVENT;
+const ADD_EVENT = common.ADD_EVENT;
+const ALL_EVENT = common.ALL_EVENT;
+/**
+ * Export `NodeWatcher` class.
+ * Watches `dir`.
+ *
+ * @class NodeWatcher
+ * @param {String} dir
+ * @param {Object} opts
+ * @public
+ */
+
+module.exports = class NodeWatcher extends EventEmitter {
+ constructor(dir, opts) {
+ super();
+ common.assignOptions(this, opts);
+ this.watched = Object.create(null);
+ this.changeTimers = Object.create(null);
+ this.dirRegistery = Object.create(null);
+ this.root = path.resolve(dir);
+ this.watchdir = this.watchdir.bind(this);
+ this.register = this.register.bind(this);
+ this.checkedEmitError = this.checkedEmitError.bind(this);
+ this.watchdir(this.root);
+ common.recReaddir(
+ this.root,
+ this.watchdir,
+ this.register,
+ this.emit.bind(this, 'ready'),
+ this.checkedEmitError,
+ this.ignored
+ );
+ }
+ /**
+ * Register files that matches our globs to know what to type of event to
+ * emit in the future.
+ *
+ * Registery looks like the following:
+ *
+ * dirRegister => Map {
+ * dirpath => Map {
+ * filename => true
+ * }
+ * }
+ *
+ * @param {string} filepath
+ * @return {boolean} whether or not we have registered the file.
+ * @private
+ */
+
+ register(filepath) {
+ const relativePath = path.relative(this.root, filepath);
+
+ if (
+ !common.isFileIncluded(this.globs, this.dot, this.doIgnore, relativePath)
+ ) {
+ return false;
+ }
+
+ const dir = path.dirname(filepath);
+
+ if (!this.dirRegistery[dir]) {
+ this.dirRegistery[dir] = Object.create(null);
+ }
+
+ const filename = path.basename(filepath);
+ this.dirRegistery[dir][filename] = true;
+ return true;
+ }
+ /**
+ * Removes a file from the registery.
+ *
+ * @param {string} filepath
+ * @private
+ */
+
+ unregister(filepath) {
+ const dir = path.dirname(filepath);
+
+ if (this.dirRegistery[dir]) {
+ const filename = path.basename(filepath);
+ delete this.dirRegistery[dir][filename];
+ }
+ }
+ /**
+ * Removes a dir from the registery.
+ *
+ * @param {string} dirpath
+ * @private
+ */
+
+ unregisterDir(dirpath) {
+ if (this.dirRegistery[dirpath]) {
+ delete this.dirRegistery[dirpath];
+ }
+ }
+ /**
+ * Checks if a file or directory exists in the registery.
+ *
+ * @param {string} fullpath
+ * @return {boolean}
+ * @private
+ */
+
+ registered(fullpath) {
+ const dir = path.dirname(fullpath);
+ return (
+ this.dirRegistery[fullpath] ||
+ (this.dirRegistery[dir] &&
+ this.dirRegistery[dir][path.basename(fullpath)])
+ );
+ }
+ /**
+ * Emit "error" event if it's not an ignorable event
+ *
+ * @param error
+ * @private
+ */
+
+ checkedEmitError(error) {
+ if (!isIgnorableFileError(error)) {
+ this.emit('error', error);
+ }
+ }
+ /**
+ * Watch a directory.
+ *
+ * @param {string} dir
+ * @private
+ */
+
+ watchdir(dir) {
+ if (this.watched[dir]) {
+ return;
+ }
+
+ const watcher = fs.watch(
+ dir,
+ {
+ persistent: true
+ },
+ this.normalizeChange.bind(this, dir)
+ );
+ this.watched[dir] = watcher;
+ watcher.on('error', this.checkedEmitError);
+
+ if (this.root !== dir) {
+ this.register(dir);
+ }
+ }
+ /**
+ * Stop watching a directory.
+ *
+ * @param {string} dir
+ * @private
+ */
+
+ stopWatching(dir) {
+ if (this.watched[dir]) {
+ this.watched[dir].close();
+ delete this.watched[dir];
+ }
+ }
+ /**
+ * End watching.
+ *
+ * @public
+ */
+
+ close() {
+ Object.keys(this.watched).forEach(this.stopWatching, this);
+ this.removeAllListeners();
+ return Promise.resolve();
+ }
+ /**
+ * On some platforms, as pointed out on the fs docs (most likely just win32)
+ * the file argument might be missing from the fs event. Try to detect what
+ * change by detecting if something was deleted or the most recent file change.
+ *
+ * @param {string} dir
+ * @param {string} event
+ * @param {string} file
+ * @public
+ */
+
+ detectChangedFile(dir, event, callback) {
+ if (!this.dirRegistery[dir]) {
+ return;
+ }
+
+ let found = false;
+ let closest = {
+ mtime: 0
+ };
+ let c = 0;
+ Object.keys(this.dirRegistery[dir]).forEach(function (file, i, arr) {
+ fs.lstat(path.join(dir, file), (error, stat) => {
+ if (found) {
+ return;
+ }
+
+ if (error) {
+ if (isIgnorableFileError(error)) {
+ found = true;
+ callback(file);
+ } else {
+ this.emit('error', error);
+ }
+ } else {
+ if (stat.mtime > closest.mtime) {
+ stat.file = file;
+ closest = stat;
+ }
+
+ if (arr.length === ++c) {
+ callback(closest.file);
+ }
+ }
+ });
+ }, this);
+ }
+ /**
+ * Normalize fs events and pass it on to be processed.
+ *
+ * @param {string} dir
+ * @param {string} event
+ * @param {string} file
+ * @public
+ */
+
+ normalizeChange(dir, event, file) {
+ if (!file) {
+ this.detectChangedFile(dir, event, actualFile => {
+ if (actualFile) {
+ this.processChange(dir, event, actualFile);
+ }
+ });
+ } else {
+ this.processChange(dir, event, path.normalize(file));
+ }
+ }
+ /**
+ * Process changes.
+ *
+ * @param {string} dir
+ * @param {string} event
+ * @param {string} file
+ * @public
+ */
+
+ processChange(dir, event, file) {
+ const fullPath = path.join(dir, file);
+ const relativePath = path.join(path.relative(this.root, dir), file);
+ fs.lstat(fullPath, (error, stat) => {
+ if (error && error.code !== 'ENOENT') {
+ this.emit('error', error);
+ } else if (!error && stat.isDirectory()) {
+ // win32 emits usless change events on dirs.
+ if (event !== 'change') {
+ this.watchdir(fullPath);
+
+ if (
+ common.isFileIncluded(
+ this.globs,
+ this.dot,
+ this.doIgnore,
+ relativePath
+ )
+ ) {
+ this.emitEvent(ADD_EVENT, relativePath, stat);
+ }
+ }
+ } else {
+ const registered = this.registered(fullPath);
+
+ if (error && error.code === 'ENOENT') {
+ this.unregister(fullPath);
+ this.stopWatching(fullPath);
+ this.unregisterDir(fullPath);
+
+ if (registered) {
+ this.emitEvent(DELETE_EVENT, relativePath);
+ }
+ } else if (registered) {
+ this.emitEvent(CHANGE_EVENT, relativePath, stat);
+ } else {
+ if (this.register(fullPath)) {
+ this.emitEvent(ADD_EVENT, relativePath, stat);
+ }
+ }
+ }
+ });
+ }
+ /**
+ * Triggers a 'change' event after debounding it to take care of duplicate
+ * events on os x.
+ *
+ * @private
+ */
+
+ emitEvent(type, file, stat) {
+ const key = type + '-' + file;
+ const addKey = ADD_EVENT + '-' + file;
+
+ if (type === CHANGE_EVENT && this.changeTimers[addKey]) {
+ // Ignore the change event that is immediately fired after an add event.
+ // (This happens on Linux).
+ return;
+ }
+
+ clearTimeout(this.changeTimers[key]);
+ this.changeTimers[key] = setTimeout(() => {
+ delete this.changeTimers[key];
+
+ if (type === ADD_EVENT && stat.isDirectory()) {
+ // Recursively emit add events and watch for sub-files/folders
+ common.recReaddir(
+ path.resolve(this.root, file),
+ function emitAddDir(dir, stats) {
+ this.watchdir(dir);
+ this.rawEmitEvent(ADD_EVENT, path.relative(this.root, dir), stats);
+ }.bind(this),
+ function emitAddFile(file, stats) {
+ this.register(file);
+ this.rawEmitEvent(ADD_EVENT, path.relative(this.root, file), stats);
+ }.bind(this),
+ function endCallback() {},
+ this.checkedEmitError,
+ this.ignored
+ );
+ } else {
+ this.rawEmitEvent(type, file, stat);
+ }
+ }, DEFAULT_DELAY);
+ }
+ /**
+ * Actually emit the events
+ */
+
+ rawEmitEvent(type, file, stat) {
+ this.emit(type, file, this.root, stat);
+ this.emit(ALL_EVENT, type, file, this.root, stat);
+ }
+};
+/**
+ * Determine if a given FS error can be ignored
+ *
+ * @private
+ */
+
+function isIgnorableFileError(error) {
+ return (
+ error.code === 'ENOENT' || // Workaround Windows node issue #4337.
+ (error.code === 'EPERM' && platform === 'win32')
+ );
+}