Skip to content

Move the ava-files package back into core #1196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 19, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const uniqueTempDir = require('unique-temp-dir');
const findCacheDir = require('find-cache-dir');
const resolveCwd = require('resolve-cwd');
const debounce = require('lodash.debounce');
const AvaFiles = require('ava-files');
const autoBind = require('auto-bind');
const Promise = require('bluebird');
const getPort = require('get-port');
Expand All @@ -16,6 +15,7 @@ const ms = require('ms');
const CachingPrecompiler = require('./lib/caching-precompiler');
const RunStatus = require('./lib/run-status');
const AvaError = require('./lib/ava-error');
const AvaFiles = require('./lib/ava-files');
const fork = require('./lib/fork');

function resolveModules(modules) {
Expand Down
309 changes: 309 additions & 0 deletions lib/ava-files.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
'use strict';
var fs = require('fs');
var path = require('path');
var Promise = require('bluebird');
var slash = require('slash');
var globby = require('globby');
var flatten = require('lodash.flatten');
var autoBind = require('auto-bind');
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 defaultHelperPatterns() {
return [
'**/__tests__/helpers/**/*.js',
'**/__tests__/**/_*.js',
'**/test/helpers/**/*.js',
'**/test/**/_*.js'
];
}

function AvaFiles(options) {
if (!(this instanceof AvaFiles)) {
throw new TypeError('Class constructor AvaFiles cannot be invoked without \'new\'');
}

options = options || {};

var files = (options.files || []).map(function (file) {
// `./` should be removed from the beginning of patterns because
// otherwise they won't match change events from Chokidar
if (file.slice(0, 2) === './') {
return file.slice(2);
}

return file;
});

if (files.length === 0) {
files = defaultIncludePatterns();
}

this.excludePatterns = defaultExcludePatterns();
this.files = files;
this.sources = options.sources || [];
this.cwd = options.cwd || process.cwd();

autoBind(this);
}

AvaFiles.prototype.findTestFiles = function () {
return handlePaths(this.files, this.excludePatterns, {
cwd: this.cwd,
cache: Object.create(null),
statCache: Object.create(null),
realpathCache: Object.create(null),
symlinks: Object.create(null)
});
};

AvaFiles.prototype.findTestHelpers = function () {
return handlePaths(defaultHelperPatterns(), ['!**/node_modules/**'], {
cwd: this.cwd,
includeUnderscoredFiles: true,
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.isSource = function (filePath) {
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);
}

filePath = matchable(filePath);

// Ignore paths outside the current working directory. They can't be matched
// to a pattern.
if (/^\.\.\//.test(filePath)) {
return false;
}

var isSource = multimatch(filePath, mixedPatterns).length === 1;
if (!isSource) {
return false;
}

var isIgnored = multimatch(filePath, defaultIgnorePatterns).length === 1;
if (!isIgnored) {
return true;
}

var isErroneouslyIgnored = multimatch(filePath, overrideDefaultIgnorePatterns).length === 1;
if (isErroneouslyIgnored) {
return true;
}

return false;
};

AvaFiles.prototype.isTest = function (filePath) {
var excludePatterns = this.excludePatterns;
var initialPatterns = this.files.concat(excludePatterns);

// 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) {
file = path.resolve(globOptions.cwd, 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';
})
.filter(function (file) {
if (path.basename(file)[0] === '_' && globOptions.includeUnderscoredFiles !== true) {
return false;
}

return true;
})
.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;
2 changes: 1 addition & 1 deletion lib/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const chokidar = require('chokidar');
const flatten = require('arr-flatten');
const union = require('array-union');
const uniq = require('array-uniq');
const AvaFiles = require('ava-files');
const AvaFiles = require('./ava-files');

function rethrowAsync(err) {
// Don't swallow exceptions. Note that any
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
"array-uniq": "^1.0.2",
"arrify": "^1.0.0",
"auto-bind": "^0.1.0",
"ava-files": "^1.0.0",
"ava-init": "^0.2.0",
"babel-code-frame": "^6.16.0",
"babel-core": "^6.17.0",
Expand Down Expand Up @@ -124,6 +123,7 @@
"find-cache-dir": "^0.1.1",
"fn-name": "^2.0.0",
"get-port": "^2.1.0",
"globby": "^6.0.0",
"has-flag": "^2.0.0",
"ignore-by-default": "^1.0.0",
"indent-string": "^3.0.0",
Expand All @@ -136,13 +136,15 @@
"last-line-stream": "^1.0.0",
"lodash.debounce": "^4.0.3",
"lodash.difference": "^4.3.0",
"lodash.flatten": "^4.2.0",
"lodash.isequal": "^4.4.0",
"loud-rejection": "^1.2.0",
"matcher": "^0.1.1",
"max-timeout": "^1.0.0",
"md5-hex": "^1.2.0",
"meow": "^3.7.0",
"ms": "^0.7.1",
"multimatch": "^2.1.0",
"observable-to-promise": "^0.4.0",
"option-chain": "^0.1.0",
"package-hash": "^1.1.0",
Expand All @@ -156,6 +158,7 @@
"require-precompiled": "^0.1.0",
"resolve-cwd": "^1.0.0",
"semver": "^5.3.0",
"slash": "^1.0.0",
"source-map-support": "^0.4.0",
"stack-utils": "^0.4.0",
"strip-ansi": "^3.0.1",
Expand Down
Loading