diff --git a/package.json b/package.json index f34829f..486ce6e 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ }, "lint-staged": { "*.js": [ - "nitpik", "eslint --fix --ignore-pattern '!.eslintrc.js'" ] }, diff --git a/src/config-array.js b/src/config-array.js index c12d26d..454483a 100644 --- a/src/config-array.js +++ b/src/config-array.js @@ -149,7 +149,7 @@ function normalizeSync(items, context, extraConfigTypes) { function shouldIgnoreFilePath(ignores, filePath, relativeFilePath) { // all files outside of the basePath are ignored - if (relativeFilePath.startsWith("..")) { + if (relativeFilePath.startsWith('..')) { return true; } @@ -314,57 +314,57 @@ export class ConfigArray extends Array { * definitions to use for the ConfigArray schema. * @param {Array} [options.configTypes] List of config types supported. */ - constructor(configs,{ - basePath = '', - normalized = false, - schema: customSchema, - extraConfigTypes = [] - } = {} - ) { + constructor(configs, { + basePath = '', + normalized = false, + schema: customSchema, + extraConfigTypes = [] + } = {} +) { super(); /** - * Tracks if the array has been normalized. - * @property isNormalized - * @type boolean - * @private - */ + * Tracks if the array has been normalized. + * @property isNormalized + * @type boolean + * @private + */ this[ConfigArraySymbol.isNormalized] = normalized; /** - * The schema used for validating and merging configs. - * @property schema - * @type ObjectSchema - * @private - */ + * The schema used for validating and merging configs. + * @property schema + * @type ObjectSchema + * @private + */ this[ConfigArraySymbol.schema] = new ObjectSchema({ ...customSchema, ...baseSchema }); /** - * The path of the config file that this array was loaded from. - * This is used to calculate filename matches. - * @property basePath - * @type string - */ + * The path of the config file that this array was loaded from. + * This is used to calculate filename matches. + * @property basePath + * @type string + */ this.basePath = basePath; assertExtraConfigTypes(extraConfigTypes); /** - * The supported config types. - * @property configTypes - * @type Array - */ + * The supported config types. + * @property configTypes + * @type Array + */ this.extraConfigTypes = Object.freeze([...extraConfigTypes]); /** - * A cache to store calculated configs for faster repeat lookup. - * @property configCache - * @type Map - * @private - */ + * A cache to store calculated configs for faster repeat lookup. + * @property configCache + * @type Map + * @private + */ this[ConfigArraySymbol.configCache] = new Map(); // init cache @@ -372,158 +372,164 @@ export class ConfigArray extends Array { // load the configs into this array if (Array.isArray(configs)) { - this.push(...configs); + this.push(...configs); } else { - this.push(configs); + this.push(configs); } } - /** +/** * Prevent normal array methods from creating a new `ConfigArray` instance. * This is to ensure that methods such as `slice()` won't try to create a * new instance of `ConfigArray` behind the scenes as doing so may throw * an error due to the different constructor signature. * @returns {Function} The `Array` constructor. */ - static get [Symbol.species]() { - return Array; - } +static get [Symbol.species]() { + return Array; +} - /** +/** * Returns the `files` globs from every config object in the array. * 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 {Array} An array of matchers. */ - get files() { +get files() { - assertNormalized(this); + assertNormalized(this); - // if this data has been cached, retrieve it - const cache = dataCache.get(this); + // if this data has been cached, retrieve it + const cache = dataCache.get(this); - if (cache.files) { - return cache.files; - } + if (cache.files) { + return cache.files; + } - // otherwise calculate it + // otherwise calculate it - const result = []; + const result = []; - for (const config of this) { - if (config.files) { - config.files.forEach(filePattern => { - result.push(filePattern); - }); - } + for (const config of this) { + if (config.files) { + config.files.forEach(filePattern => { + result.push(filePattern); + }); } + } - // store result - cache.files = result; - dataCache.set(this, cache); + // store result + cache.files = result; + dataCache.set(this, cache); - return result; - } + return result; +} - /** +/** * 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 and functions to be ignored. */ - get ignores() { +get ignores() { - assertNormalized(this); + assertNormalized(this); - // if this data has been cached, retrieve it - const cache = dataCache.get(this); + // if this data has been cached, retrieve it + const cache = dataCache.get(this); - if (cache.ignores) { - return cache.ignores; - } + if (cache.ignores) { + return cache.ignores; + } - // otherwise calculate it + // otherwise calculate it - const result = []; + const result = []; - for (const config of this) { - if (config.ignores && !config.files) { - result.push(...config.ignores); - } + for (const config of this) { + + /* + * 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) { + result.push(...config.ignores); } + } - // store result - cache.ignores = result; - dataCache.set(this, cache); + // store result + cache.ignores = result; + dataCache.set(this, cache); - return result; - } + return result; +} - /** +/** * Indicates if the config array has been normalized. * @returns {boolean} True if the config array is normalized, false if not. */ - isNormalized() { - return this[ConfigArraySymbol.isNormalized]; - } +isNormalized() { + return this[ConfigArraySymbol.isNormalized]; +} - /** +/** * Normalizes a config array by flattening embedded arrays and executing * config functions. * @param {ConfigContext} context The context object for config functions. * @returns {Promise} The current ConfigArray instance. */ - async normalize(context = {}) { +async normalize(context = {}) { - if (!this.isNormalized()) { - const normalizedConfigs = await normalize(this, context, this.extraConfigTypes); - this.length = 0; - this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this))); - this[ConfigArraySymbol.isNormalized] = true; - - // prevent further changes - Object.freeze(this); - } + if (!this.isNormalized()) { + const normalizedConfigs = await normalize(this, context, this.extraConfigTypes); + this.length = 0; + this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this))); + this[ConfigArraySymbol.isNormalized] = true; - return this; + // 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; +normalizeSync(context = {}) { - // prevent further changes - Object.freeze(this); - } + 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; - return this; + // prevent further changes + Object.freeze(this); } - /** + return this; +} + +/** * Finalizes the state of a config before being cached and returned by * `getConfig()`. Does nothing by default but is provided to be * overridden by subclasses as necessary. * @param {Object} config The config to finalize. * @returns {Object} The finalized config. */ - [ConfigArraySymbol.finalizeConfig](config) { - return config; - } +[ConfigArraySymbol.finalizeConfig](config) { + return config; +} - /** +/** * Preprocesses a config during the normalization process. This is the * method to override if you want to convert an array item before it is * validated for the first time. For example, if you want to replace a @@ -531,11 +537,11 @@ export class ConfigArray extends Array { * @param {Object} config The config to preprocess. * @returns {Object} The config to use in place of the argument. */ - [ConfigArraySymbol.preprocessConfig](config) { - return config; - } +[ConfigArraySymbol.preprocessConfig](config) { + 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. @@ -543,125 +549,125 @@ export class ConfigArray extends Array { * @returns {boolean} True if the file path matches a `files` entry * or false if not. */ - isExplicitMatch(filePath) { - - assertNormalized(this); +isExplicitMatch(filePath) { - const cache = dataCache.get(this); + assertNormalized(this); - // first check the cache to avoid duplicate work - let result = cache.explicitMatches.get(filePath); + const cache = dataCache.get(this); - if (typeof result == "boolean") { - return result; - } + // first check the cache to avoid duplicate work + let result = cache.explicitMatches.get(filePath); - // TODO: Maybe move elsewhere? Maybe combine with getConfig() logic? - const relativeFilePath = path.relative(this.basePath, filePath); + if (typeof result == 'boolean') { + return result; + } - if (shouldIgnoreFilePath(this.ignores, filePath, relativeFilePath)) { - debug(`Ignoring ${filePath}`); + // TODO: Maybe move elsewhere? Maybe combine with getConfig() logic? + const relativeFilePath = path.relative(this.basePath, filePath); - // cache and return result - cache.explicitMatches.set(filePath, false); - return false; - } + if (shouldIgnoreFilePath(this.ignores, filePath, relativeFilePath)) { + debug(`Ignoring ${filePath}`); - // filePath isn't automatically ignored, so try to find a match + // cache and return result + cache.explicitMatches.set(filePath, false); + return false; + } - for (const config of this) { + // filePath isn't automatically ignored, so try to find a match - if (!config.files) { - continue; - } + for (const config of this) { - if (pathMatches(filePath, this.basePath, config)) { - debug(`Matching config found for ${filePath}`); - cache.explicitMatches.set(filePath, true); - return true; - } + if (!config.files) { + continue; } - return false; + 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. * @returns {Object} The config object for this file. */ - getConfig(filePath) { - - assertNormalized(this); +getConfig(filePath) { - // first check the cache to avoid duplicate work - let finalConfig = this[ConfigArraySymbol.configCache].get(filePath); + assertNormalized(this); - if (finalConfig) { - return finalConfig; - } + // first check the cache to avoid duplicate work + let finalConfig = this[ConfigArraySymbol.configCache].get(filePath); - // TODO: Maybe move elsewhere? - const relativeFilePath = path.relative(this.basePath, filePath); + if (finalConfig) { + return finalConfig; + } - if (shouldIgnoreFilePath(this.ignores, filePath, relativeFilePath)) { - debug(`Ignoring ${filePath}`); + // TODO: Maybe move elsewhere? + const relativeFilePath = path.relative(this.basePath, filePath); - // cache and return result - finalConfig is undefined at this point - this[ConfigArraySymbol.configCache].set(filePath, finalConfig); - return finalConfig; - } + if (shouldIgnoreFilePath(this.ignores, filePath, relativeFilePath)) { + debug(`Ignoring ${filePath}`); - // filePath isn't automatically ignored, so try to construct config + // cache and return result - finalConfig is undefined at this point + this[ConfigArraySymbol.configCache].set(filePath, finalConfig); + return finalConfig; + } - const matchingConfigs = []; - let matchFound = false; + // filePath isn't automatically ignored, so try to construct config - for (const config of this) { + const matchingConfigs = []; + let matchFound = false; - if (!config.files) { - debug(`Universal config found for ${filePath}`); - matchingConfigs.push(config); - continue; - } + for (const config of this) { - if (pathMatches(filePath, this.basePath, config)) { - debug(`Matching config found for ${filePath}`); - matchingConfigs.push(config); - matchFound = true; - continue; - } - } - - // 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 - this[ConfigArraySymbol.configCache].set(filePath, finalConfig); - return finalConfig; + if (!config.files) { + debug(`Universal config found for ${filePath}`); + matchingConfigs.push(config); + continue; } - // otherwise construct the config - - finalConfig = matchingConfigs.reduce((result, config) => { - return this[ConfigArraySymbol.schema].merge(result, config); - }, {}, this); + if (pathMatches(filePath, this.basePath, config)) { + debug(`Matching config found for ${filePath}`); + matchingConfigs.push(config); + matchFound = true; + continue; + } + } - finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig); + // 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 this[ConfigArraySymbol.configCache].set(filePath, finalConfig); - return finalConfig; } - /** + // otherwise construct the config + + finalConfig = matchingConfigs.reduce((result, config) => { + return this[ConfigArraySymbol.schema].merge(result, config); + }, {}, this); + + finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig); + + this[ConfigArraySymbol.configCache].set(filePath, 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. */ - isIgnored(filePath) { - return this.getConfig(filePath) === undefined; - } +isIgnored(filePath) { + return this.getConfig(filePath) === undefined; +} }