haste-resolver
Version:
Haste Resolver
430 lines (377 loc) • 13 kB
JavaScript
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _get = function get(_x2, _x3, _x4) { var _again = true; _function: while (_again) { var object = _x2, property = _x3, receiver = _x4; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x2 = parent; _x3 = property; _x4 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var denodeify = require('denodeify');
var _require = require('events');
var EventEmitter = _require.EventEmitter;
var fs = require('graceful-fs');
var path = require('./fastpath');
var readFile = denodeify(fs.readFile);
var _stat = denodeify(fs.stat);
var NOT_FOUND_IN_ROOTS = 'NotFoundInRootsError';
var Fastfs = (function (_EventEmitter) {
_inherits(Fastfs, _EventEmitter);
function Fastfs(name, roots, fileWatcher, _ref) {
var ignore = _ref.ignore;
var crawling = _ref.crawling;
var activity = _ref.activity;
_classCallCheck(this, Fastfs);
_get(Object.getPrototypeOf(Fastfs.prototype), 'constructor', this).call(this);
this._name = name;
this._fileWatcher = fileWatcher;
this._ignore = ignore;
this._roots = roots.map(function (root) {
// If the path ends in a separator ("/"), remove it to make string
// operations on paths safer.
if (root.endsWith(path.sep)) {
root = root.substr(0, root.length - 1);
}
root = path.resolve(root);
return new File(root, true);
});
this._fastPaths = Object.create(null);
this._crawling = crawling;
this._activity = activity;
}
_createClass(Fastfs, [{
key: 'build',
value: function build() {
var _this = this;
return this._crawling.then(function (files) {
var fastfsActivity = undefined;
var activity = _this._activity;
if (activity) {
fastfsActivity = activity.startEvent('Building in-memory fs for ' + _this._name);
}
files.forEach(function (filePath) {
var root = _this._getRoot(filePath);
if (root) {
var newFile = new File(filePath, false);
var dirname = filePath.substr(0, filePath.lastIndexOf(path.sep));
var _parent = _this._fastPaths[dirname];
_this._fastPaths[filePath] = newFile;
if (_parent) {
_parent.addChild(newFile, _this._fastPaths);
} else {
root.addChild(newFile, _this._fastPaths);
}
}
});
if (activity) {
activity.endEvent(fastfsActivity);
}
if (_this._fileWatcher) {
_this._fileWatcher.on('all', _this._processFileChange.bind(_this));
}
});
}
}, {
key: 'stat',
value: function stat(filePath) {
var _this2 = this;
return Promise.resolve().then(function () {
return _this2._getFile(filePath).stat();
});
}
}, {
key: 'getAllFiles',
value: function getAllFiles() {
var _this3 = this;
return Object.keys(this._fastPaths).filter(function (filePath) {
return !_this3._fastPaths[filePath].isDir;
});
}
}, {
key: 'findFilesByExts',
value: function findFilesByExts(exts) {
var _ref2 = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
var ignore = _ref2.ignore;
return this.getAllFiles().filter(function (filePath) {
return exts.indexOf(path.extname(filePath).substr(1)) !== -1 && (!ignore || !ignore(filePath));
});
}
}, {
key: 'matchFilesByPattern',
value: function matchFilesByPattern(pattern) {
return this.getAllFiles().filter(function (file) {
return file.match(pattern);
});
}
}, {
key: 'readFile',
value: function readFile(filePath) {
var file = this._getFile(filePath);
if (!file) {
throw new Error('Unable to find file with path: ' + filePath);
}
return file.read();
}
}, {
key: 'readWhile',
value: function readWhile(filePath, predicate) {
var file = this._getFile(filePath);
if (!file) {
throw new Error('Unable to find file with path: ' + filePath);
}
return file.readWhile(predicate);
}
}, {
key: 'closest',
value: function closest(filePath, name) {
for (var file = this._getFile(filePath).parent; file; file = file.parent) {
if (file.children[name]) {
return file.children[name].path;
}
}
return null;
}
}, {
key: 'fileExists',
value: function fileExists(filePath) {
var file = undefined;
try {
file = this._getFile(filePath);
} catch (e) {
if (e.type === NOT_FOUND_IN_ROOTS) {
return false;
}
throw e;
}
return file && !file.isDir;
}
}, {
key: 'dirExists',
value: function dirExists(filePath) {
var file = undefined;
try {
file = this._getFile(filePath);
} catch (e) {
if (e.type === NOT_FOUND_IN_ROOTS) {
return false;
}
throw e;
}
return file && file.isDir;
}
}, {
key: 'matches',
value: function matches(dir, pattern) {
var dirFile = this._getFile(dir);
if (!dirFile.isDir) {
throw new Error('Expected file ' + dirFile.path + ' to be a directory');
}
return Object.keys(dirFile.children).filter(function (name) {
return name.match(pattern);
}).map(function (name) {
return path.join(dirFile.path, name);
});
}
}, {
key: '_getRoot',
value: function _getRoot(filePath) {
for (var i = 0; i < this._roots.length; i++) {
var possibleRoot = this._roots[i];
if (isDescendant(possibleRoot.path, filePath)) {
return possibleRoot;
}
}
return null;
}
}, {
key: '_getAndAssertRoot',
value: function _getAndAssertRoot(filePath) {
var root = this._getRoot(filePath);
if (!root) {
var error = new Error('File ' + filePath + ' not found in any of the roots');
error.type = NOT_FOUND_IN_ROOTS;
throw error;
}
return root;
}
}, {
key: '_getFile',
value: function _getFile(filePath) {
filePath = path.resolve(filePath);
if (!this._fastPaths[filePath]) {
var file = this._getAndAssertRoot(filePath).getFileFromPath(filePath);
if (file) {
this._fastPaths[filePath] = file;
}
}
return this._fastPaths[filePath];
}
}, {
key: '_processFileChange',
value: function _processFileChange(type, filePath, rootPath, fstat) {
var absPath = path.join(rootPath, filePath);
if (this._ignore(absPath) || fstat && fstat.isDirectory()) {
return;
}
// Make sure this event belongs to one of our roots.
var root = this._getRoot(absPath);
if (!root) {
return;
}
if (type === 'delete' || type === 'change') {
var file = this._getFile(absPath);
if (file) {
file.remove();
}
}
delete this._fastPaths[path.resolve(absPath)];
if (type !== 'delete') {
var file = new File(absPath, false);
root.addChild(file, this._fastPaths);
}
this.emit('change', type, filePath, rootPath, fstat);
}
}]);
return Fastfs;
})(EventEmitter);
var File = (function () {
function File(filePath, isDir) {
_classCallCheck(this, File);
this.path = filePath;
this.isDir = isDir;
this.children = this.isDir ? Object.create(null) : null;
}
_createClass(File, [{
key: 'read',
value: function read() {
if (!this._read) {
this._read = readFile(this.path, 'utf8');
}
return this._read;
}
}, {
key: 'readWhile',
value: function readWhile(predicate) {
var _this4 = this;
return _readWhile(this.path, predicate).then(function (_ref3) {
var result = _ref3.result;
var completed = _ref3.completed;
if (completed && !_this4._read) {
_this4._read = Promise.resolve(result);
}
return result;
});
}
}, {
key: 'stat',
value: function stat() {
if (!this._stat) {
this._stat = _stat(this.path);
}
return this._stat;
}
}, {
key: 'addChild',
value: function addChild(file, fileMap) {
var parts = file.path.substr(this.path.length + 1).split(path.sep);
if (parts.length === 1) {
this.children[parts[0]] = file;
file.parent = this;
} else if (this.children[parts[0]]) {
this.children[parts[0]].addChild(file, fileMap);
} else {
var dir = new File(this.path + path.sep + parts[0], true);
dir.parent = this;
this.children[parts[0]] = dir;
fileMap[dir.path] = dir;
dir.addChild(file, fileMap);
}
}
}, {
key: 'getFileFromPath',
value: function getFileFromPath(filePath) {
var parts = path.relative(this.path, filePath).split(path.sep);
/*eslint consistent-this:0*/
var file = this;
for (var i = 0; i < parts.length; i++) {
var fileName = parts[i];
if (!fileName) {
continue;
}
if (!file || !file.isDir) {
// File not found.
return null;
}
file = file.children[fileName];
}
return file;
}
}, {
key: 'ext',
value: function ext() {
return path.extname(this.path).substr(1);
}
}, {
key: 'remove',
value: function remove() {
if (!this.parent) {
throw new Error('No parent to delete ' + this.path + ' from');
}
delete this.parent.children[path.basename(this.path)];
}
}]);
return File;
})();
function _readWhile(filePath, predicate) {
return new Promise(function (resolve, reject) {
fs.open(filePath, 'r', function (openError, fd) {
if (openError) {
reject(openError);
return;
}
read(fd,
/*global Buffer: true*/
new Buffer(512), makeReadCallback(fd, predicate, function (readError, result, completed) {
if (readError) {
reject(readError);
} else {
resolve({ result: result, completed: completed });
}
}));
});
});
}
function read(fd, buffer, callback) {
fs.read(fd, buffer, 0, buffer.length, -1, callback);
}
function close(fd, error, result, complete, callback) {
fs.close(fd, function (closeError) {
return callback(error || closeError, result, complete);
});
}
function makeReadCallback(fd, predicate, callback) {
var result = '';
var index = 0;
return function readCallback(error, bytesRead, buffer) {
if (error) {
close(fd, error, undefined, false, callback);
return;
}
var completed = bytesRead === 0;
var chunk = completed ? '' : buffer.toString('utf8', 0, bytesRead);
result += chunk;
if (completed || !predicate(chunk, index++, result)) {
close(fd, null, result, completed, callback);
} else {
read(fd, buffer, readCallback);
}
};
}
function isDescendant(root, child) {
return child.startsWith(root);
}
module.exports = Fastfs;