Bump packages to fix linter

This commit is contained in:
Henry Mercer 2023-01-18 20:50:03 +00:00
parent ed9506bbaf
commit 0a11e3fdd9
6063 changed files with 378752 additions and 306784 deletions

198
node_modules/@humanwhocodes/config-array/CHANGELOG.md generated vendored Normal file
View file

@ -0,0 +1,198 @@
# Changelog
## [0.11.8](https://github.com/humanwhocodes/config-array/compare/v0.11.7...v0.11.8) (2022-12-14)
### Bug Fixes
* Ensure gitignore-style directory ignores ([#74](https://github.com/humanwhocodes/config-array/issues/74)) ([8e17f4a](https://github.com/humanwhocodes/config-array/commit/8e17f4a7378cb0b417e1103d60ef397b26d2f917))
## [0.11.7](https://github.com/humanwhocodes/config-array/compare/v0.11.6...v0.11.7) (2022-10-28)
### Bug Fixes
* **deps:** Update minimatch to secure version ([3219294](https://github.com/humanwhocodes/config-array/commit/3219294bf9170c500ee9e212b59e17ef205b7c3c))
## [0.11.6](https://github.com/humanwhocodes/config-array/compare/v0.11.5...v0.11.6) (2022-10-21)
### Bug Fixes
* Only apply universal patterns if others match. ([e69c8fd](https://github.com/humanwhocodes/config-array/commit/e69c8fdbb7696b406821bc723b86b4c5304c4260))
## [0.11.5](https://github.com/humanwhocodes/config-array/compare/v0.11.4...v0.11.5) (2022-10-17)
### Bug Fixes
* Unignoring of directories should work ([e1c9dcd](https://github.com/humanwhocodes/config-array/commit/e1c9dcd05534619effe258596191ea9dc5bb37af))
## [0.11.4](https://github.com/humanwhocodes/config-array/compare/v0.11.3...v0.11.4) (2022-10-14)
### Bug Fixes
* Ensure subdirectories of ignored directories are ignored ([0df450e](https://github.com/humanwhocodes/config-array/commit/0df450eabeb595ae22fe680ce3320dc47edb1e66))
## [0.11.3](https://github.com/humanwhocodes/config-array/compare/v0.11.2...v0.11.3) (2022-10-13)
### Bug Fixes
* Ensure directories can be unignored. ([206404c](https://github.com/humanwhocodes/config-array/commit/206404c490d354a4f39ef9b4a6d0ceaec119abc5))
## [0.11.2](https://github.com/humanwhocodes/config-array/compare/v0.11.1...v0.11.2) (2022-10-03)
### Bug Fixes
* Error conditions for isDirectoryIgnored ([0bd81f5](https://github.com/humanwhocodes/config-array/commit/0bd81f53b7c217d561f70709057c7d77f17e8c6d))
* isDirectoryIgnored should match on relative path. ([3d1eaf6](https://github.com/humanwhocodes/config-array/commit/3d1eaf6389056215e27793cde9c2954c01c78df8))
* isFileIgnored should call isDirectoryIgnored ([270d359](https://github.com/humanwhocodes/config-array/commit/270d359295f376edb0c73905f62a848284d34053))
### Performance Improvements
* Cache isDirectoryIgnored calls ([c5e6720](https://github.com/humanwhocodes/config-array/commit/c5e67208618e253c08bd320efeae4b1f63641e63))
## [0.11.1](https://github.com/humanwhocodes/config-array/compare/v0.11.0...v0.11.1) (2022-09-30)
### Bug Fixes
* isDirectoryIgnored should not test negated patterns ([f6cdb68](https://github.com/humanwhocodes/config-array/commit/f6cdb688784901970fda72eb688eb1a00c44b09a))
## [0.11.0](https://github.com/humanwhocodes/config-array/compare/v0.10.7...v0.11.0) (2022-09-30)
### Features
* Add isDirectoryIgnored; deprecated isIgnored ([e6942f2](https://github.com/humanwhocodes/config-array/commit/e6942f2ce075007d39f23530593b7adb19178a52))
## [0.10.7](https://github.com/humanwhocodes/config-array/compare/v0.10.6...v0.10.7) (2022-09-29)
### Bug Fixes
* Cache negated patterns separately ([fef617b](https://github.com/humanwhocodes/config-array/commit/fef617b6999f9a4b5871d4525c82c4181bc96fb7))
## [0.10.6](https://github.com/humanwhocodes/config-array/compare/v0.10.5...v0.10.6) (2022-09-28)
### Performance Improvements
* Cache Minimatch instances ([5cf9af7](https://github.com/humanwhocodes/config-array/commit/5cf9af7ecaf227d2106be0cebd92d7f5148867e6))
## [0.10.5](https://github.com/humanwhocodes/config-array/compare/v0.10.4...v0.10.5) (2022-09-21)
### Bug Fixes
* Improve caching to improve performance ([#50](https://github.com/humanwhocodes/config-array/issues/50)) ([8a7e8ab](https://github.com/humanwhocodes/config-array/commit/8a7e8ab499bcbb10d7cbdd676197fc686966a64e))
### [0.10.4](https://www.github.com/humanwhocodes/config-array/compare/v0.10.3...v0.10.4) (2022-07-29)
### Bug Fixes
* Global ignores only when no other keys ([1f6b6ae](https://www.github.com/humanwhocodes/config-array/commit/1f6b6ae89152c1ebe118f55e7ea05c37e7c960dc))
* Re-introduce ignores fixes ([b3ec560](https://www.github.com/humanwhocodes/config-array/commit/b3ec560c485bec2f7420fd63a939448b49a073e3))
### [0.10.3](https://www.github.com/humanwhocodes/config-array/compare/v0.10.2...v0.10.3) (2022-07-20)
### Bug Fixes
* Ensure preprocess method has correct 'this' value. ([f86933a](https://www.github.com/humanwhocodes/config-array/commit/f86933a072e5a4069bab2c1ce284dedf0efa715d))
### [0.10.2](https://www.github.com/humanwhocodes/config-array/compare/v0.10.1...v0.10.2) (2022-03-18)
### Bug Fixes
* Files outside of basePath should be ignored ([fc4d7b2](https://www.github.com/humanwhocodes/config-array/commit/fc4d7b2e851959ab9ab84305f6c78c52e9cc2c3c))
### [0.10.1](https://www.github.com/humanwhocodes/config-array/compare/v0.10.0...v0.10.1) (2022-03-03)
### Bug Fixes
* Explicit matching is required against files field ([ab4e428](https://www.github.com/humanwhocodes/config-array/commit/ab4e4282ecea994ef88d273dc47aa24bf3c6972e))
## [0.10.0](https://www.github.com/humanwhocodes/config-array/compare/v0.9.5...v0.10.0) (2022-03-01)
### Features
* Add isExplicitMatch() method ([9ecd90e](https://www.github.com/humanwhocodes/config-array/commit/9ecd90e2a3e984633f535daa4da3cbfb96964fdd))
### [0.9.5](https://www.github.com/humanwhocodes/config-array/compare/v0.9.4...v0.9.5) (2022-02-23)
### Bug Fixes
* Ensure dot directories are matched correctly ([6e8d180](https://www.github.com/humanwhocodes/config-array/commit/6e8d180f43cedf3c2072d8a1229470e9fafabf5b))
* preprocessConfig should have correct 'this' value ([9641540](https://www.github.com/humanwhocodes/config-array/commit/96415402cf0012ccf8e4af6c7b934dfc1a058986))
### [0.9.4](https://www.github.com/humanwhocodes/config-array/compare/v0.9.3...v0.9.4) (2022-01-27)
### Bug Fixes
* Negated patterns to work when files match ([398c811](https://www.github.com/humanwhocodes/config-array/commit/398c8119d359493dc7b82b40df4d92ea6528375f))
### [0.9.3](https://www.github.com/humanwhocodes/config-array/compare/v0.9.2...v0.9.3) (2022-01-26)
### Bug Fixes
* Make negated ignore patterns work like gitignore ([4ee8e99](https://www.github.com/humanwhocodes/config-array/commit/4ee8e998436e2c4538b06476e0bead8a44fe5a1b))
### [0.9.2](https://www.github.com/humanwhocodes/config-array/compare/v0.9.1...v0.9.2) (2021-11-02)
### Bug Fixes
* Object merging error by upgrading object-schema ([377d06d](https://www.github.com/humanwhocodes/config-array/commit/377d06d2a44d781b0bec70b3389c48b3d5a63f94))
### [0.9.1](https://www.github.com/humanwhocodes/config-array/compare/v0.9.0...v0.9.1) (2021-10-05)
### Bug Fixes
* Properly build package for release ([168155f](https://www.github.com/humanwhocodes/config-array/commit/168155f3fed91ab35566c452efd28debf8ec2b85))
## [0.9.0](https://www.github.com/humanwhocodes/config-array/compare/v0.8.0...v0.9.0) (2021-10-04)
### Features
* getConfig() now returns undefined when no configs match. ([a563b82](https://www.github.com/humanwhocodes/config-array/commit/a563b8255d4eb2bb7745314e3f00ef53792b343f))
## [0.8.0](https://www.github.com/humanwhocodes/config-array/compare/v0.7.0...v0.8.0) (2021-10-01)
### Features
* Add isIgnored() method ([343e5a0](https://www.github.com/humanwhocodes/config-array/commit/343e5a0a9e32028bfc6c0bf1ec0c6badf74f47f9))
### Bug Fixes
* Ensure global ignores are honored ([343e5a0](https://www.github.com/humanwhocodes/config-array/commit/343e5a0a9e32028bfc6c0bf1ec0c6badf74f47f9))
## [0.7.0](https://www.github.com/humanwhocodes/config-array/compare/v0.6.0...v0.7.0) (2021-09-24)
### Features
* Only object configs by default ([5645f24](https://www.github.com/humanwhocodes/config-array/commit/5645f241b2412a3263a02ef9e3a9bd19cc86035d))
## [0.6.0](https://www.github.com/humanwhocodes/config-array/compare/v0.5.0...v0.6.0) (2021-04-20)
### Features
* Add the normalizeSync() method ([3e347f9](https://www.github.com/humanwhocodes/config-array/commit/3e347f9d77c5ca2b15995e75ff7bc4fb96b7d66e))
* Allow async config functions ([a9def0f](https://www.github.com/humanwhocodes/config-array/commit/a9def0faf579c223349dfe08d2486756840538c3))

View file

@ -74,11 +74,11 @@ const configs = new ConfigArray(rawConfigs, {
});
```
This example reads in an object or array from `my.config.js` and passes it into the `ConfigArray` constructor as the first argument. The second argument is an object specifying the `basePath` (the directoy in which `my.config.js` is found) and a `schema` to define the additional properties of a config object beyond `files`, `ignores`, and `name`.
This example reads in an object or array from `my.config.js` and passes it into the `ConfigArray` constructor as the first argument. The second argument is an object specifying the `basePath` (the directory in which `my.config.js` is found) and a `schema` to define the additional properties of a config object beyond `files`, `ignores`, and `name`.
### Specifying a Schema
The `schema` option is required for you to use additional properties in config objects. The schema is object that follows the format of an [`ObjectSchema`](https://npmjs.com/package/@humanwhocodes/object-schema). The schema specifies both validation and merge rules that the `ConfigArray` instance needs to combine configs when there are multiple matches. Here's an example:
The `schema` option is required for you to use additional properties in config objects. The schema is an object that follows the format of an [`ObjectSchema`](https://npmjs.com/package/@humanwhocodes/object-schema). The schema specifies both validation and merge rules that the `ConfigArray` instance needs to combine configs when there are multiple matches. Here's an example:
```js
const configFilename = path.resolve(process.cwd(), "my.config.js");
@ -106,14 +106,17 @@ const configs = new ConfigArray(rawConfigs, {
// the path to match filenames from
basePath: process.cwd(),
// additional items in each config
schema: mySchema
// additional item schemas in each config
schema: mySchema,
// additional config types supported (default: [])
extraConfigTypes: ["array", "function"];
});
```
### Config Arrays
Config arrays can be multidimensional, so it's possible for a config array to contain another config array, such as:
Config arrays can be multidimensional, so it's possible for a config array to contain another config array when `extraConfigTypes` contains `"array"`, such as:
```js
export default [
@ -171,11 +174,44 @@ If the `files` array contains a function, then that function is called with the
If the `files` array contains an item that is an array of strings and functions, then all patterns must match in order for the config to match. In the preceding examples, both `*.test.*` and `*.js` must match in order for the config object to be used.
If a pattern in the files array begins with `!` then it excludes that pattern. In the preceding example, any filename that doesn't end with `.js` will automatically getting a `settings.js` property set to `false`.
If a pattern in the files array begins with `!` then it excludes that pattern. In the preceding example, any filename that doesn't end with `.js` will automatically get a `settings.js` property set to `false`.
You can also specify an `ignores` key that will force files matching those patterns to not be included. If the `ignores` key is in a config object without any other keys, then those ignores will always be applied; otherwise those ignores act as exclusions. Here's an example:
```js
export default [
// Always ignored
{
ignores: ["**/.git/**", "**/node_modules/**"]
},
// .eslintrc.js file is ignored only when .js file matches
{
files: ["**/*.js"],
ignores: [".eslintrc.js"]
handler: jsHandler
}
];
```
You can use negated patterns in `ignores` to exclude a file that was already ignored, such as:
```js
export default [
// Ignore all JSON files except tsconfig.json
{
files: ["**/*"],
ignores: ["**/*.json", "!tsconfig.json"]
},
];
```
### Config Functions
Config arrays can also include config functions. A config function accepts a single parameter, `context` (defined by you), and must return either a config object or a config array (it cannot return another function). Config functions allow end users to execute code in the creation of appropriate config objects. Here's an example:
Config arrays can also include config functions when `extraConfigTypes` contains `"function"`. A config function accepts a single parameter, `context` (defined by you), and must return either a config object or a config array (it cannot return another function). Config functions allow end users to execute code in the creation of appropriate config objects. Here's an example:
```js
export default [
@ -210,7 +246,7 @@ export default [
When a config array is normalized, each function is executed and replaced in the config array with the return value.
**Note:** Config functions cannot be async. This will be added in a future version.
**Note:** Config functions can also be async.
### Normalizing Config Arrays
@ -226,6 +262,14 @@ await configs.normalize({
The `normalize()` method returns a promise, so be sure to use the `await` operator. The config array instance is normalized in-place, so you don't need to create a new variable.
If you want to disallow async config functions, you can call `normalizeSync()` instead. This method is completely synchronous and does not require using the `await` operator as it does not return a promise:
```js
await configs.normalizeSync({
name: "MyApp"
});
```
**Important:** Once a `ConfigArray` is normalized, it cannot be changed further. You can, however, create a new `ConfigArray` and pass in the normalized instance to create an unnormalized copy.
### Getting Config for a File
@ -244,6 +288,46 @@ A few things to keep in mind:
* You must pass in the absolute filename to get a config for.
* The returned config object never has `files`, `ignores`, or `name` properties; the only properties on the object will be the other configuration options specified.
* The config array caches configs, so subsequent calls to `getConfig()` with the same filename will return in a fast lookup rather than another calculation.
* A config will only be generated if the filename matches an entry in a `files` key. A config will not be generated without matching a `files` key (configs without a `files` key are only applied when another config with a `files` key is applied; configs without `files` are never applied on their own). Any config with a `files` key entry ending with `/**` or `/*` will only be applied if another entry in the same `files` key matches or another config matches.
## Determining Ignored Paths
You can determine if a file is ignored by using the `isFileIgnored()` method and passing in the absolute path of any file, as in this example:
```js
const ignored = configs.isFileIgnored('/foo/bar/baz.txt');
```
A file is considered ignored if any of the following is true:
* **It's parent directory is ignored.** For example, if `foo` is in `ignores`, then `foo/a.js` is considered ignored.
* **It has an ancestor directory that is ignored.** For example, if `foo` is in `ignores`, then `foo/baz/a.js` is considered ignored.
* **It matches an ignored file pattern.** For example, if `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **If it matches an entry in `files` and also in `ignores`.** For example, if `**/*.js` is in `files` and `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **The file is outside the `basePath`.** If the `basePath` is `/usr/me`, then `/foo/a.js` is considered ignored.
For directories, use the `isDirectoryIgnored()` method and pass in the absolute path of any directory, as in this example:
```js
const ignored = configs.isDirectoryIgnored('/foo/bar/');
```
A directory is considered ignored if any of the following is true:
* **It's parent directory is ignored.** For example, if `foo` is in `ignores`, then `foo/baz` is considered ignored.
* **It has an ancestor directory that is ignored.** For example, if `foo` is in `ignores`, then `foo/bar/baz/a.js` is considered ignored.
* **It matches and ignored file pattern.** For example, if `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **If it matches an entry in `files` and also in `ignores`.** For example, if `**/*.js` is in `files` and `**/a.js` is in `ignores`, then `foo/a.js` and `foo/baz/a.js` are considered ignored.
* **The file is outside the `basePath`.** If the `basePath` is `/usr/me`, then `/foo/a.js` is considered ignored.
**Important:** A pattern such as `foo/**` means that `foo` and `foo/` are *not* ignored whereas `foo/bar` is ignored. If you want to ignore `foo` and all of its subdirectories, use the pattern `foo` or `foo/` in `ignores`.
## Caching Mechanisms
Each `ConfigArray` aggressively caches configuration objects to avoid unnecessary work. This caching occurs in two ways:
1. **File-based Caching.** For each filename that is passed into a method, the resulting config is cached against that filename so you're always guaranteed to get the same object returned from `getConfig()` whenever you pass the same filename in.
2. **Index-based Caching.** Whenever a config is calculated, the config elements that were used to create the config are also cached. So if a given filename matches elements 1, 5, and 7, the resulting config is cached with a key of `1,5,7`. That way, if another file is passed that matches the same config elements, the result is already known and doesn't have to be recalculated. That means two files that match all the same elements will return the same config from `getConfig()`.
## Acknowledgements

View file

@ -103,12 +103,18 @@ const baseSchema = Object.freeze({
// Helpers
//------------------------------------------------------------------------------
const Minimatch = minimatch.Minimatch;
const minimatchCache = new Map();
const negatedMinimatchCache = new Map();
const debug = createDebug('@hwc/config-array');
const MINIMATCH_OPTIONS = {
matchBase: true
// matchBase: true,
dot: true
};
const CONFIG_TYPES = new Set(['array', 'function']);
/**
* Shorthand for checking if a value is a string.
* @param {any} value The value to check.
@ -118,27 +124,135 @@ function isString(value) {
return typeof value === 'string';
}
/**
* Asserts that the files key of a config object is a nonempty array.
* @param {object} config The config object to check.
* @returns {void}
* @throws {TypeError} If the files key isn't a nonempty array.
*/
function assertNonEmptyFilesArray(config) {
if (!Array.isArray(config.files) || config.files.length === 0) {
throw new TypeError('The files key must be a non-empty array.');
}
}
/**
* Wrapper around minimatch that caches minimatch patterns for
* faster matching speed over multiple file path evaluations.
* @param {string} filepath The file path to match.
* @param {string} pattern The glob pattern to match against.
* @param {object} options The minimatch options to use.
* @returns
*/
function doMatch(filepath, pattern, options = {}) {
let cache = minimatchCache;
if (options.flipNegate) {
cache = negatedMinimatchCache;
}
let matcher = cache.get(pattern);
if (!matcher) {
matcher = new Minimatch(pattern, Object.assign({}, MINIMATCH_OPTIONS, options));
cache.set(pattern, matcher);
}
return matcher.match(filepath);
}
/**
* Normalizes a `ConfigArray` by flattening it and executing any functions
* that are found inside.
* @param {Array} items The items in a `ConfigArray`.
* @param {Object} context The context object to pass into any function
* found.
* @returns {Array} A flattened array containing only config objects.
* @param {Array<string>} extraConfigTypes The config types to check.
* @returns {Promise<Array>} A flattened array containing only config objects.
* @throws {TypeError} When a config function returns a function.
*/
async function normalize(items, context) {
async function normalize(items, context, extraConfigTypes) {
// TODO: Allow async config functions
const allowFunctions = extraConfigTypes.includes('function');
const allowArrays = extraConfigTypes.includes('array');
function *flatTraverse(array) {
async function* flatTraverse(array) {
for (let item of array) {
if (typeof item === 'function') {
if (!allowFunctions) {
throw new TypeError('Unexpected function.');
}
item = item(context);
if (item.then) {
item = await item;
}
}
if (Array.isArray(item)) {
yield * flatTraverse(item);
if (!allowArrays) {
throw new TypeError('Unexpected array.');
}
yield* flatTraverse(item);
} else if (typeof item === 'function') {
throw new TypeError('A config function can only return an object or array.');
} else {
yield item;
}
}
}
/*
* Async iterables cannot be used with the spread operator, so we need to manually
* create the array to return.
*/
const asyncIterable = await flatTraverse(items);
const configs = [];
for await (const config of asyncIterable) {
configs.push(config);
}
return configs;
}
/**
* Normalizes a `ConfigArray` by flattening it and executing any functions
* that are found inside.
* @param {Array} items The items in a `ConfigArray`.
* @param {Object} context The context object to pass into any function
* found.
* @param {Array<string>} extraConfigTypes The config types to check.
* @returns {Array} A flattened array containing only config objects.
* @throws {TypeError} When a config function returns a function.
*/
function normalizeSync(items, context, extraConfigTypes) {
const allowFunctions = extraConfigTypes.includes('function');
const allowArrays = extraConfigTypes.includes('array');
function* flatTraverse(array) {
for (let item of array) {
if (typeof item === 'function') {
if (!allowFunctions) {
throw new TypeError('Unexpected function.');
}
item = item(context);
if (item.then) {
throw new TypeError('Async config functions are not supported.');
}
}
if (Array.isArray(item)) {
if (!allowArrays) {
throw new TypeError('Unexpected array.');
}
yield* flatTraverse(item);
} else if (typeof item === 'function') {
throw new TypeError('A config function can only return an object or array.');
} else {
@ -150,6 +264,52 @@ async function normalize(items, context) {
return [...flatTraverse(items)];
}
/**
* Determines if a given file path should be ignored based on the given
* matcher.
* @param {Array<string|() => boolean>} ignores The ignore patterns to check.
* @param {string} filePath The absolute path of the file to check.
* @param {string} relativeFilePath The relative path of the file to check.
* @returns {boolean} True if the path should be ignored and false if not.
*/
function shouldIgnorePath(ignores, filePath, relativeFilePath) {
// all files outside of the basePath are ignored
if (relativeFilePath.startsWith('..')) {
return true;
}
return ignores.reduce((ignored, matcher) => {
if (!ignored) {
if (typeof matcher === 'function') {
return matcher(filePath);
}
// don't check negated patterns because we're not ignored yet
if (!matcher.startsWith('!')) {
return doMatch(relativeFilePath, matcher);
}
// otherwise we're still not ignored
return false;
}
// only need to check negated patterns because we're ignored
if (typeof matcher === 'string' && matcher.startsWith('!')) {
return !doMatch(relativeFilePath, matcher, {
flipNegate: true
});
}
return ignored;
}, false);
}
/**
* Determines if a given file path is matched by a config. If the config
* has no `files` field, then it matches; otherwise, if a `files` field
@ -162,31 +322,32 @@ async function normalize(items, context) {
*/
function pathMatches(filePath, basePath, config) {
// a config without a `files` field always matches
if (!config.files) {
return true;
}
/*
* For both files and ignores, functions are passed the absolute
* file path while strings are compared against the relative
* file path.
*/
const relativeFilePath = path.relative(basePath, filePath);
// if files isn't an array, throw an error
if (!Array.isArray(config.files) || config.files.length === 0) {
throw new TypeError('The files key must be a non-empty array.');
}
const relativeFilePath = path.relative(basePath, filePath);
assertNonEmptyFilesArray(config);
// match both strings and functions
const match = pattern => {
if (isString(pattern)) {
return minimatch(relativeFilePath, pattern, MINIMATCH_OPTIONS);
return doMatch(relativeFilePath, pattern);
}
if (typeof pattern === 'function') {
return pattern(filePath);
}
throw new TypeError(`Unexpected matcher type ${pattern}.`);
};
// check for all matches to config.files
let matches = config.files.some(pattern => {
let filePathMatchesPattern = config.files.some(pattern => {
if (Array.isArray(pattern)) {
return pattern.every(match);
}
@ -198,13 +359,11 @@ function pathMatches(filePath, basePath, config) {
* If the file path matches the config.files patterns, then check to see
* if there are any files to ignore.
*/
if (matches && config.ignores) {
matches = !config.ignores.some(pattern => {
return minimatch(filePath, pattern, MINIMATCH_OPTIONS);
});
if (filePathMatchesPattern && config.ignores) {
filePathMatchesPattern = !shouldIgnorePath(config.ignores, filePath, relativeFilePath);
}
return matches;
return filePathMatchesPattern;
}
/**
@ -220,6 +379,24 @@ function assertNormalized(configArray) {
}
}
/**
* Ensures that config types are valid.
* @param {Array<string>} extraConfigTypes The config types to check.
* @returns {void}
* @throws {Error} When the config types array is invalid.
*/
function assertExtraConfigTypes(extraConfigTypes) {
if (extraConfigTypes.length > 2) {
throw new TypeError('configTypes must be an array with at most two items.');
}
for (const configType of extraConfigTypes) {
if (!CONFIG_TYPES.has(configType)) {
throw new TypeError(`Unexpected config type "${configType}" found. Expected one of: "object", "array", "function".`);
}
}
}
//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------
@ -232,6 +409,9 @@ const ConfigArraySymbol = {
preprocessConfig: Symbol('preprocessConfig')
};
// used to store calculate data for faster lookup
const dataCache = new WeakMap();
/**
* Represents an array of config objects and provides method for working with
* those config objects.
@ -247,8 +427,15 @@ class ConfigArray extends Array {
* configs have already been normalized.
* @param {Object} [options.schema] The additional schema
* definitions to use for the ConfigArray schema.
* @param {Array<string>} [options.configTypes] List of config types supported.
*/
constructor(configs, { basePath = '', normalized = false, schema: customSchema } = {}) {
constructor(configs, {
basePath = '',
normalized = false,
schema: customSchema,
extraConfigTypes = []
} = {}
) {
super();
/**
@ -265,10 +452,9 @@ class ConfigArray extends Array {
* @type ObjectSchema
* @private
*/
this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema({
...customSchema,
...baseSchema
});
this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema(
Object.assign({}, customSchema, baseSchema)
);
/**
* The path of the config file that this array was loaded from.
@ -278,6 +464,15 @@ class ConfigArray extends Array {
*/
this.basePath = basePath;
assertExtraConfigTypes(extraConfigTypes);
/**
* The supported config types.
* @property configTypes
* @type Array<string>
*/
this.extraConfigTypes = Object.freeze([...extraConfigTypes]);
/**
* A cache to store calculated configs for faster repeat lookup.
* @property configCache
@ -286,6 +481,14 @@ class ConfigArray extends Array {
*/
this[ConfigArraySymbol.configCache] = new Map();
// init cache
dataCache.set(this, {
explicitMatches: new Map(),
directoryMatches: new Map(),
files: undefined,
ignores: undefined
});
// load the configs into this array
if (Array.isArray(configs)) {
this.push(...configs);
@ -308,54 +511,110 @@ class ConfigArray extends Array {
/**
* Returns the `files` globs from every config object in the array.
* Negated patterns (those beginning with `!`) are not returned.
* This can be used to determine which files will be matched by a
* config array or to use as a glob pattern when no patterns are provided
* for a command line interface.
* @returns {string[]} An array of string patterns.
* @returns {Array<string|Function>} An array of matchers.
*/
get files() {
assertNormalized(this);
// if this data has been cached, retrieve it
const cache = dataCache.get(this);
if (cache.files) {
return cache.files;
}
// otherwise calculate it
const result = [];
for (const config of this) {
if (config.files) {
config.files.forEach(filePattern => {
if (Array.isArray(filePattern)) {
result.push(...filePattern.filter(pattern => {
return isString(pattern) && !pattern.startsWith('!');
}));
} else if (isString(filePattern) && !filePattern.startsWith('!')) {
result.push(filePattern);
}
result.push(filePattern);
});
}
}
// store result
cache.files = result;
dataCache.set(this, cache);
return result;
}
/**
* Returns the file globs that should always be ignored regardless of
* Returns ignore matchers that should always be ignored regardless of
* the matching `files` fields in any configs. This is necessary to mimic
* the behavior of things like .gitignore and .eslintignore, allowing a
* globbing operation to be faster.
* @returns {string[]} An array of string patterns to be ignored.
* @returns {string[]} An array of string patterns and functions to be ignored.
*/
get ignores() {
assertNormalized(this);
// if this data has been cached, retrieve it
const cache = dataCache.get(this);
if (cache.ignores) {
return cache.ignores;
}
// otherwise calculate it
const result = [];
for (const config of this) {
if (config.ignores && !config.files) {
result.push(...config.ignores.filter(isString));
/*
* We only count ignores if there are no other keys in the object.
* In this case, it acts list a globally ignored pattern. If there
* are additional keys, then ignores act like exclusions.
*/
if (config.ignores && Object.keys(config).length === 1) {
/*
* If there are directory ignores, then we need to double up
* the patterns to be ignored. For instance, `foo` will also
* need `foo/**` in order to account for subdirectories.
*/
config.ignores.forEach(ignore => {
result.push(ignore);
if (typeof ignore === 'string') {
// unignoring files won't work unless we unignore directories too
if (ignore.startsWith('!')) {
if (ignore.endsWith('/**')) {
result.push(ignore.slice(0, ignore.length - 3));
} else if (ignore.endsWith('/*')) {
result.push(ignore.slice(0, ignore.length - 2));
}
}
// directories should work with or without a trailing slash
if (ignore.endsWith('/')) {
result.push(ignore.slice(0, ignore.length - 1));
result.push(ignore + '**');
} else if (!ignore.endsWith('*')) {
result.push(ignore + '/**');
}
}
});
}
}
// store result
cache.ignores = result;
dataCache.set(this, cache);
return result;
}
@ -371,14 +630,35 @@ class ConfigArray extends Array {
* Normalizes a config array by flattening embedded arrays and executing
* config functions.
* @param {ConfigContext} context The context object for config functions.
* @returns {ConfigArray} A new ConfigArray instance that is normalized.
* @returns {Promise<ConfigArray>} The current ConfigArray instance.
*/
async normalize(context = {}) {
if (!this.isNormalized()) {
const normalizedConfigs = await normalize(this, context);
const normalizedConfigs = await normalize(this, context, this.extraConfigTypes);
this.length = 0;
this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig]));
this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this)));
this[ConfigArraySymbol.isNormalized] = true;
// prevent further changes
Object.freeze(this);
}
return this;
}
/**
* Normalizes a config array by flattening embedded arrays and executing
* config functions.
* @param {ConfigContext} context The context object for config functions.
* @returns {ConfigArray} The current ConfigArray instance.
*/
normalizeSync(context = {}) {
if (!this.isNormalized()) {
const normalizedConfigs = normalizeSync(this, context, this.extraConfigTypes);
this.length = 0;
this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this)));
this[ConfigArraySymbol.isNormalized] = true;
// prevent further changes
@ -411,6 +691,56 @@ class ConfigArray extends Array {
return config;
}
/**
* Determines if a given file path explicitly matches a `files` entry
* and also doesn't match an `ignores` entry. Configs that don't have
* a `files` property are not considered an explicit match.
* @param {string} filePath The complete path of a file to check.
* @returns {boolean} True if the file path matches a `files` entry
* or false if not.
*/
isExplicitMatch(filePath) {
assertNormalized(this);
const cache = dataCache.get(this);
// first check the cache to avoid duplicate work
let result = cache.explicitMatches.get(filePath);
if (typeof result == 'boolean') {
return result;
}
// TODO: Maybe move elsewhere? Maybe combine with getConfig() logic?
const relativeFilePath = path.relative(this.basePath, filePath);
if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) {
debug(`Ignoring ${filePath}`);
// cache and return result
cache.explicitMatches.set(filePath, false);
return false;
}
// filePath isn't automatically ignored, so try to find a match
for (const config of this) {
if (!config.files) {
continue;
}
if (pathMatches(filePath, this.basePath, config)) {
debug(`Matching config found for ${filePath}`);
cache.explicitMatches.set(filePath, true);
return true;
}
}
return false;
}
/**
* Returns the config object for a given file path.
* @param {string} filePath The complete path of a file to get a config for.
@ -420,37 +750,229 @@ class ConfigArray extends Array {
assertNormalized(this);
// first check the cache to avoid duplicate work
let finalConfig = this[ConfigArraySymbol.configCache].get(filePath);
const cache = this[ConfigArraySymbol.configCache];
// first check the cache for a filename match to avoid duplicate work
let finalConfig = cache.get(filePath);
if (finalConfig) {
return finalConfig;
}
// No config found in cache, so calculate a new one
// next check to see if the file should be ignored
const matchingConfigs = [];
// check if this should be ignored due to its directory
if (this.isDirectoryIgnored(path.dirname(filePath))) {
debug(`Ignoring ${filePath} based on directory pattern`);
for (const config of this) {
if (pathMatches(filePath, this.basePath, config)) {
debug(`Matching config found for ${filePath}`);
matchingConfigs.push(config);
} else {
debug(`No matching config found for ${filePath}`);
}
// cache and return result - finalConfig is undefined at this point
cache.set(filePath, finalConfig);
return finalConfig;
}
finalConfig = matchingConfigs.reduce((result, config) => {
return this[ConfigArraySymbol.schema].merge(result, config);
// TODO: Maybe move elsewhere?
const relativeFilePath = path.relative(this.basePath, filePath);
if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) {
debug(`Ignoring ${filePath} based on file pattern`);
// cache and return result - finalConfig is undefined at this point
cache.set(filePath, finalConfig);
return finalConfig;
}
// filePath isn't automatically ignored, so try to construct config
const matchingConfigIndices = [];
let matchFound = false;
const universalPattern = /\/\*{1,2}$/;
this.forEach((config, index) => {
if (!config.files) {
debug(`Anonymous universal config found for ${filePath}`);
matchingConfigIndices.push(index);
return;
}
assertNonEmptyFilesArray(config);
/*
* If a config has a files pattern ending in /** or /*, and the
* filePath only matches those patterns, then the config is only
* applied if there is another config where the filePath matches
* a file with a specific extensions such as *.js.
*/
const universalFiles = config.files.filter(
pattern => universalPattern.test(pattern)
);
// universal patterns were found so we need to check the config twice
if (universalFiles.length) {
debug('Universal files patterns found. Checking carefully.');
const nonUniversalFiles = config.files.filter(
pattern => !universalPattern.test(pattern)
);
// check that the config matches without the non-universal files first
if (
nonUniversalFiles.length &&
pathMatches(
filePath, this.basePath,
{ files: nonUniversalFiles, ignores: config.ignores }
)
) {
debug(`Matching config found for ${filePath}`);
matchingConfigIndices.push(index);
matchFound = true;
return;
}
// if there wasn't a match then check if it matches with universal files
if (
universalFiles.length &&
pathMatches(
filePath, this.basePath,
{ files: universalFiles, ignores: config.ignores }
)
) {
debug(`Matching config found for ${filePath}`);
matchingConfigIndices.push(index);
return;
}
// if we make here, then there was no match
return;
}
// the normal case
if (pathMatches(filePath, this.basePath, config)) {
debug(`Matching config found for ${filePath}`);
matchingConfigIndices.push(index);
matchFound = true;
return;
}
});
// if matching both files and ignores, there will be no config to create
if (!matchFound) {
debug(`No matching configs found for ${filePath}`);
// cache and return result - finalConfig is undefined at this point
cache.set(filePath, finalConfig);
return finalConfig;
}
// check to see if there is a config cached by indices
finalConfig = cache.get(matchingConfigIndices.toString());
if (finalConfig) {
// also store for filename for faster lookup next time
cache.set(filePath, finalConfig);
return finalConfig;
}
// otherwise construct the config
finalConfig = matchingConfigIndices.reduce((result, index) => {
return this[ConfigArraySymbol.schema].merge(result, this[index]);
}, {}, this);
finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig);
this[ConfigArraySymbol.configCache].set(filePath, finalConfig);
cache.set(filePath, finalConfig);
cache.set(matchingConfigIndices.toString(), finalConfig);
return finalConfig;
}
/**
* Determines if the given filepath is ignored based on the configs.
* @param {string} filePath The complete path of a file to check.
* @returns {boolean} True if the path is ignored, false if not.
* @deprecated Use `isFileIgnored` instead.
*/
isIgnored(filePath) {
return this.isFileIgnored(filePath);
}
/**
* Determines if the given filepath is ignored based on the configs.
* @param {string} filePath The complete path of a file to check.
* @returns {boolean} True if the path is ignored, false if not.
*/
isFileIgnored(filePath) {
return this.getConfig(filePath) === undefined;
}
/**
* Determines if the given directory is ignored based on the configs.
* This checks only default `ignores` that don't have `files` in the
* same config. A pattern such as `/foo` be considered to ignore the directory
* while a pattern such as `/foo/**` is not considered to ignore the
* directory because it is matching files.
* @param {string} directoryPath The complete path of a directory to check.
* @returns {boolean} True if the directory is ignored, false if not. Will
* return true for any directory that is not inside of `basePath`.
* @throws {Error} When the `ConfigArray` is not normalized.
*/
isDirectoryIgnored(directoryPath) {
assertNormalized(this);
const relativeDirectoryPath = path.relative(this.basePath, directoryPath)
.replace(/\\/g, '/');
if (relativeDirectoryPath.startsWith('..')) {
return true;
}
// first check the cache
const cache = dataCache.get(this).directoryMatches;
if (cache.has(relativeDirectoryPath)) {
return cache.get(relativeDirectoryPath);
}
const directoryParts = relativeDirectoryPath.split('/');
let relativeDirectoryToCheck = '';
let result = false;
/*
* In order to get the correct gitignore-style ignores, where an
* ignored parent directory cannot have any descendants unignored,
* we need to check every directory starting at the parent all
* the way down to the actual requested directory.
*
* We aggressively cache all of this info to make sure we don't
* have to recalculate everything for every call.
*/
do {
relativeDirectoryToCheck += directoryParts.shift() + '/';
result = shouldIgnorePath(
this.ignores,
path.join(this.basePath, relativeDirectoryToCheck),
relativeDirectoryToCheck
);
cache.set(relativeDirectoryToCheck, result);
} while (!result && directoryParts.length);
// also cache the result for the requested path
cache.set(relativeDirectoryPath, result);
return result;
}
}
exports.ConfigArray = ConfigArray;

View file

@ -1,6 +1,6 @@
{
"name": "@humanwhocodes/config-array",
"version": "0.5.0",
"version": "0.11.8",
"description": "Glob-based configuration matching.",
"author": "Nicholas C. Zakas",
"main": "api.js",
@ -19,6 +19,7 @@
"build": "rollup -c",
"format": "nitpik",
"lint": "eslint *.config.js src/*.js tests/*.js",
"lint:fix": "eslint --fix *.config.js src/*.js tests/*.js",
"prepublish": "npm run build",
"test:coverage": "nyc --include src/*.js npm run test",
"test": "mocha -r esm tests/ --recursive"
@ -28,7 +29,6 @@
},
"lint-staged": {
"*.js": [
"nitpik",
"eslint --fix --ignore-pattern '!.eslintrc.js'"
]
},
@ -42,20 +42,20 @@
"node": ">=10.10.0"
},
"dependencies": {
"@humanwhocodes/object-schema": "^1.2.0",
"@humanwhocodes/object-schema": "^1.2.1",
"debug": "^4.1.1",
"minimatch": "^3.0.4"
"minimatch": "^3.0.5"
},
"devDependencies": {
"@nitpik/javascript": "^0.3.3",
"@nitpik/javascript": "0.4.0",
"@nitpik/node": "0.0.5",
"chai": "^4.2.0",
"eslint": "^6.7.1",
"esm": "^3.2.25",
"lint-staged": "^10.2.8",
"mocha": "^6.1.4",
"nyc": "^14.1.1",
"rollup": "^1.12.3",
"yorkie": "^2.0.0"
"chai": "4.3.7",
"eslint": "8.29.0",
"esm": "3.2.25",
"lint-staged": "13.1.0",
"mocha": "6.2.3",
"nyc": "14.1.1",
"rollup": "1.16.6",
"yorkie": "2.0.0"
}
}
}