You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

263 lines
7.0 KiB
JavaScript

6 years ago
var fs = require('fs');
var path = require('path');
var Promise = require('bluebird');
var slash = require('slash');
var globby = require('globby');
var flatten = require('arr-flatten');
var defaultIgnore = require('ignore-by-default').directories();
var multimatch = require('multimatch');
function defaultExcludePatterns() {
return [
'!**/node_modules/**',
'!**/fixtures/**',
'!**/helpers/**'
];
}
function defaultIncludePatterns() {
return [
'test.js',
'test-*.js',
'test',
'**/__tests__',
'**/*.test.js'
];
}
function AvaFiles(files, sources) {
if (!(this instanceof AvaFiles)) {
throw new TypeError('Class constructor AvaFiles cannot be invoked without \'new\'');
}
if (!files || !files.length) {
files = defaultIncludePatterns();
}
this.excludePatterns = defaultExcludePatterns();
this.files = files;
this.sources = sources || [];
}
AvaFiles.prototype.findTestFiles = function () {
return handlePaths(this.files, this.excludePatterns, {
cache: Object.create(null),
statCache: Object.create(null),
realpathCache: Object.create(null),
symlinks: Object.create(null)
});
};
function getDefaultIgnorePatterns() {
return defaultIgnore.map(function (dir) {
return dir + '/**/*';
});
}
// Used on paths before they're passed to multimatch to harmonize matching
// across platforms.
var matchable = process.platform === 'win32' ? slash : function (path) {
return path;
};
AvaFiles.prototype.makeSourceMatcher = function () {
var mixedPatterns = [];
var defaultIgnorePatterns = getDefaultIgnorePatterns();
var overrideDefaultIgnorePatterns = [];
var hasPositivePattern = false;
this.sources.forEach(function (pattern) {
mixedPatterns.push(pattern);
// TODO: why not just pattern[0] !== '!'
if (!hasPositivePattern && pattern[0] !== '!') {
hasPositivePattern = true;
}
// Extract patterns that start with an ignored directory. These need to be
// rematched separately.
if (defaultIgnore.indexOf(pattern.split('/')[0]) >= 0) {
overrideDefaultIgnorePatterns.push(pattern);
}
});
// Same defaults as used for Chokidar.
if (!hasPositivePattern) {
mixedPatterns = ['package.json', '**/*.js'].concat(mixedPatterns);
}
return function (path) {
path = matchable(path);
// Ignore paths outside the current working directory. They can't be matched
// to a pattern.
if (/^\.\.\//.test(path)) {
return false;
}
var isSource = multimatch(path, mixedPatterns).length === 1;
if (!isSource) {
return false;
}
var isIgnored = multimatch(path, defaultIgnorePatterns).length === 1;
if (!isIgnored) {
return true;
}
var isErroneouslyIgnored = multimatch(path, overrideDefaultIgnorePatterns).length === 1;
if (isErroneouslyIgnored) {
return true;
}
return false;
};
};
AvaFiles.prototype.makeTestMatcher = function () {
var excludePatterns = this.excludePatterns;
var initialPatterns = this.files.concat(excludePatterns);
return function (filepath) {
// Like in api.js, tests must be .js files and not start with _
if (path.extname(filepath) !== '.js' || path.basename(filepath)[0] === '_') {
return false;
}
// Check if the entire path matches a pattern.
if (multimatch(matchable(filepath), initialPatterns).length === 1) {
return true;
}
// Check if the path contains any directory components.
var dirname = path.dirname(filepath);
if (dirname === '.') {
return false;
}
// Compute all possible subpaths. Note that the dirname is assumed to be
// relative to the working directory, without a leading `./`.
var subpaths = dirname.split(/[\\\/]/).reduce(function (subpaths, component) {
var parent = subpaths[subpaths.length - 1];
if (parent) {
// Always use / to makes multimatch consistent across platforms.
subpaths.push(parent + '/' + component);
} else {
subpaths.push(component);
}
return subpaths;
}, []);
// Check if any of the possible subpaths match a pattern. If so, generate a
// new pattern with **/*.js.
var recursivePatterns = subpaths.filter(function (subpath) {
return multimatch(subpath, initialPatterns).length === 1;
}).map(function (subpath) {
// Always use / to makes multimatch consistent across platforms.
return subpath + '/**/*.js';
});
// See if the entire path matches any of the subpaths patterns, taking the
// excludePatterns into account. This mimicks the behavior in api.js
return multimatch(matchable(filepath), recursivePatterns.concat(excludePatterns)).length === 1;
};
};
AvaFiles.prototype.getChokidarPatterns = function () {
var paths = [];
var ignored = [];
this.sources.forEach(function (pattern) {
if (pattern[0] === '!') {
ignored.push(pattern.slice(1));
} else {
paths.push(pattern);
}
});
// Allow source patterns to override the default ignore patterns. Chokidar
// ignores paths that match the list of ignored patterns. It uses anymatch
// under the hood, which supports negation patterns. For any source pattern
// that starts with an ignored directory, ensure the corresponding negation
// pattern is added to the ignored paths.
var overrideDefaultIgnorePatterns = paths.filter(function (pattern) {
return defaultIgnore.indexOf(pattern.split('/')[0]) >= 0;
}).map(function (pattern) {
return '!' + pattern;
});
ignored = getDefaultIgnorePatterns().concat(ignored, overrideDefaultIgnorePatterns);
if (paths.length === 0) {
paths = ['package.json', '**/*.js'];
}
paths = paths.concat(this.files);
return {
paths: paths,
ignored: ignored
};
};
function handlePaths(files, excludePatterns, globOptions) {
// convert pinkie-promise to Bluebird promise
files = Promise.resolve(globby(files.concat(excludePatterns), globOptions));
var searchedParents = Object.create(null);
var foundFiles = Object.create(null);
function alreadySearchingParent(dir) {
if (searchedParents[dir]) {
return true;
}
var parentDir = path.dirname(dir);
if (parentDir === dir) {
// We have reached the root path.
return false;
}
return alreadySearchingParent(parentDir);
}
return files
.map(function (file) {
if (fs.statSync(file).isDirectory()) {
if (alreadySearchingParent(file)) {
return null;
}
searchedParents[file] = true;
var pattern = path.join(file, '**', '*.js');
if (process.platform === 'win32') {
// Always use / in patterns, harmonizing matching across platforms.
pattern = slash(pattern);
}
return handlePaths([pattern], excludePatterns, globOptions);
}
// globby returns slashes even on Windows. Normalize here so the file
// paths are consistently platform-accurate as tests are run.
return path.normalize(file);
})
.then(flatten)
.filter(function (file) {
return file && path.extname(file) === '.js' && path.basename(file)[0] !== '_';
})
.map(function (file) {
return path.resolve(file);
})
.filter(function (file) {
var alreadyFound = foundFiles[file];
foundFiles[file] = true;
return !alreadyFound;
});
}
module.exports = AvaFiles;
module.exports.defaultIncludePatterns = defaultIncludePatterns;
module.exports.defaultExcludePatterns = defaultExcludePatterns;