source: imaps-frontend/node_modules/@humanwhocodes/config-array/api.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 27.8 KB
Line 
1'use strict';
2
3var path = require('path');
4var minimatch = require('minimatch');
5var createDebug = require('debug');
6var objectSchema = require('@humanwhocodes/object-schema');
7
8/**
9 * @fileoverview ConfigSchema
10 * @author Nicholas C. Zakas
11 */
12
13//------------------------------------------------------------------------------
14// Helpers
15//------------------------------------------------------------------------------
16
17const NOOP_STRATEGY = {
18 required: false,
19 merge() {
20 return undefined;
21 },
22 validate() { }
23};
24
25//------------------------------------------------------------------------------
26// Exports
27//------------------------------------------------------------------------------
28
29/**
30 * The base schema that every ConfigArray uses.
31 * @type Object
32 */
33const baseSchema = Object.freeze({
34 name: {
35 required: false,
36 merge() {
37 return undefined;
38 },
39 validate(value) {
40 if (typeof value !== 'string') {
41 throw new TypeError('Property must be a string.');
42 }
43 }
44 },
45 files: NOOP_STRATEGY,
46 ignores: NOOP_STRATEGY
47});
48
49/**
50 * @fileoverview ConfigSchema
51 * @author Nicholas C. Zakas
52 */
53
54//------------------------------------------------------------------------------
55// Helpers
56//------------------------------------------------------------------------------
57
58/**
59 * Asserts that a given value is an array.
60 * @param {*} value The value to check.
61 * @returns {void}
62 * @throws {TypeError} When the value is not an array.
63 */
64function assertIsArray(value) {
65 if (!Array.isArray(value)) {
66 throw new TypeError('Expected value to be an array.');
67 }
68}
69
70/**
71 * Asserts that a given value is an array containing only strings and functions.
72 * @param {*} value The value to check.
73 * @returns {void}
74 * @throws {TypeError} When the value is not an array of strings and functions.
75 */
76function assertIsArrayOfStringsAndFunctions(value, name) {
77 assertIsArray(value);
78
79 if (value.some(item => typeof item !== 'string' && typeof item !== 'function')) {
80 throw new TypeError('Expected array to only contain strings and functions.');
81 }
82}
83
84/**
85 * Asserts that a given value is a non-empty array.
86 * @param {*} value The value to check.
87 * @returns {void}
88 * @throws {TypeError} When the value is not an array or an empty array.
89 */
90function assertIsNonEmptyArray(value) {
91 if (!Array.isArray(value) || value.length === 0) {
92 throw new TypeError('Expected value to be a non-empty array.');
93 }
94}
95
96//------------------------------------------------------------------------------
97// Exports
98//------------------------------------------------------------------------------
99
100/**
101 * The schema for `files` and `ignores` that every ConfigArray uses.
102 * @type Object
103 */
104const filesAndIgnoresSchema = Object.freeze({
105 files: {
106 required: false,
107 merge() {
108 return undefined;
109 },
110 validate(value) {
111
112 // first check if it's an array
113 assertIsNonEmptyArray(value);
114
115 // then check each member
116 value.forEach(item => {
117 if (Array.isArray(item)) {
118 assertIsArrayOfStringsAndFunctions(item);
119 } else if (typeof item !== 'string' && typeof item !== 'function') {
120 throw new TypeError('Items must be a string, a function, or an array of strings and functions.');
121 }
122 });
123
124 }
125 },
126 ignores: {
127 required: false,
128 merge() {
129 return undefined;
130 },
131 validate: assertIsArrayOfStringsAndFunctions
132 }
133});
134
135/**
136 * @fileoverview ConfigArray
137 * @author Nicholas C. Zakas
138 */
139
140
141//------------------------------------------------------------------------------
142// Helpers
143//------------------------------------------------------------------------------
144
145const Minimatch = minimatch.Minimatch;
146const minimatchCache = new Map();
147const negatedMinimatchCache = new Map();
148const debug = createDebug('@hwc/config-array');
149
150const MINIMATCH_OPTIONS = {
151 // matchBase: true,
152 dot: true
153};
154
155const CONFIG_TYPES = new Set(['array', 'function']);
156
157const FILES_AND_IGNORES_SCHEMA = new objectSchema.ObjectSchema(filesAndIgnoresSchema);
158
159/**
160 * Shorthand for checking if a value is a string.
161 * @param {any} value The value to check.
162 * @returns {boolean} True if a string, false if not.
163 */
164function isString(value) {
165 return typeof value === 'string';
166}
167
168/**
169 * Asserts that the files and ignores keys of a config object are valid as per base schema.
170 * @param {object} config The config object to check.
171 * @returns {void}
172 * @throws {TypeError} If the files and ignores keys of a config object are not valid.
173 */
174function assertValidFilesAndIgnores(config) {
175 if (!config || typeof config !== 'object') {
176 return;
177 }
178 const validateConfig = { };
179 if ('files' in config) {
180 validateConfig.files = config.files;
181 }
182 if ('ignores' in config) {
183 validateConfig.ignores = config.ignores;
184 }
185 FILES_AND_IGNORES_SCHEMA.validate(validateConfig);
186}
187
188/**
189 * Wrapper around minimatch that caches minimatch patterns for
190 * faster matching speed over multiple file path evaluations.
191 * @param {string} filepath The file path to match.
192 * @param {string} pattern The glob pattern to match against.
193 * @param {object} options The minimatch options to use.
194 * @returns
195 */
196function doMatch(filepath, pattern, options = {}) {
197
198 let cache = minimatchCache;
199
200 if (options.flipNegate) {
201 cache = negatedMinimatchCache;
202 }
203
204 let matcher = cache.get(pattern);
205
206 if (!matcher) {
207 matcher = new Minimatch(pattern, Object.assign({}, MINIMATCH_OPTIONS, options));
208 cache.set(pattern, matcher);
209 }
210
211 return matcher.match(filepath);
212}
213
214/**
215 * Normalizes a `ConfigArray` by flattening it and executing any functions
216 * that are found inside.
217 * @param {Array} items The items in a `ConfigArray`.
218 * @param {Object} context The context object to pass into any function
219 * found.
220 * @param {Array<string>} extraConfigTypes The config types to check.
221 * @returns {Promise<Array>} A flattened array containing only config objects.
222 * @throws {TypeError} When a config function returns a function.
223 */
224async function normalize(items, context, extraConfigTypes) {
225
226 const allowFunctions = extraConfigTypes.includes('function');
227 const allowArrays = extraConfigTypes.includes('array');
228
229 async function* flatTraverse(array) {
230 for (let item of array) {
231 if (typeof item === 'function') {
232 if (!allowFunctions) {
233 throw new TypeError('Unexpected function.');
234 }
235
236 item = item(context);
237 if (item.then) {
238 item = await item;
239 }
240 }
241
242 if (Array.isArray(item)) {
243 if (!allowArrays) {
244 throw new TypeError('Unexpected array.');
245 }
246 yield* flatTraverse(item);
247 } else if (typeof item === 'function') {
248 throw new TypeError('A config function can only return an object or array.');
249 } else {
250 yield item;
251 }
252 }
253 }
254
255 /*
256 * Async iterables cannot be used with the spread operator, so we need to manually
257 * create the array to return.
258 */
259 const asyncIterable = await flatTraverse(items);
260 const configs = [];
261
262 for await (const config of asyncIterable) {
263 configs.push(config);
264 }
265
266 return configs;
267}
268
269/**
270 * Normalizes a `ConfigArray` by flattening it and executing any functions
271 * that are found inside.
272 * @param {Array} items The items in a `ConfigArray`.
273 * @param {Object} context The context object to pass into any function
274 * found.
275 * @param {Array<string>} extraConfigTypes The config types to check.
276 * @returns {Array} A flattened array containing only config objects.
277 * @throws {TypeError} When a config function returns a function.
278 */
279function normalizeSync(items, context, extraConfigTypes) {
280
281 const allowFunctions = extraConfigTypes.includes('function');
282 const allowArrays = extraConfigTypes.includes('array');
283
284 function* flatTraverse(array) {
285 for (let item of array) {
286 if (typeof item === 'function') {
287
288 if (!allowFunctions) {
289 throw new TypeError('Unexpected function.');
290 }
291
292 item = item(context);
293 if (item.then) {
294 throw new TypeError('Async config functions are not supported.');
295 }
296 }
297
298 if (Array.isArray(item)) {
299
300 if (!allowArrays) {
301 throw new TypeError('Unexpected array.');
302 }
303
304 yield* flatTraverse(item);
305 } else if (typeof item === 'function') {
306 throw new TypeError('A config function can only return an object or array.');
307 } else {
308 yield item;
309 }
310 }
311 }
312
313 return [...flatTraverse(items)];
314}
315
316/**
317 * Determines if a given file path should be ignored based on the given
318 * matcher.
319 * @param {Array<string|() => boolean>} ignores The ignore patterns to check.
320 * @param {string} filePath The absolute path of the file to check.
321 * @param {string} relativeFilePath The relative path of the file to check.
322 * @returns {boolean} True if the path should be ignored and false if not.
323 */
324function shouldIgnorePath(ignores, filePath, relativeFilePath) {
325
326 // all files outside of the basePath are ignored
327 if (relativeFilePath.startsWith('..')) {
328 return true;
329 }
330
331 return ignores.reduce((ignored, matcher) => {
332
333 if (!ignored) {
334
335 if (typeof matcher === 'function') {
336 return matcher(filePath);
337 }
338
339 // don't check negated patterns because we're not ignored yet
340 if (!matcher.startsWith('!')) {
341 return doMatch(relativeFilePath, matcher);
342 }
343
344 // otherwise we're still not ignored
345 return false;
346
347 }
348
349 // only need to check negated patterns because we're ignored
350 if (typeof matcher === 'string' && matcher.startsWith('!')) {
351 return !doMatch(relativeFilePath, matcher, {
352 flipNegate: true
353 });
354 }
355
356 return ignored;
357
358 }, false);
359
360}
361
362/**
363 * Determines if a given file path is matched by a config based on
364 * `ignores` only.
365 * @param {string} filePath The absolute file path to check.
366 * @param {string} basePath The base path for the config.
367 * @param {Object} config The config object to check.
368 * @returns {boolean} True if the file path is matched by the config,
369 * false if not.
370 */
371function pathMatchesIgnores(filePath, basePath, config) {
372
373 /*
374 * For both files and ignores, functions are passed the absolute
375 * file path while strings are compared against the relative
376 * file path.
377 */
378 const relativeFilePath = path.relative(basePath, filePath);
379
380 return Object.keys(config).length > 1 &&
381 !shouldIgnorePath(config.ignores, filePath, relativeFilePath);
382}
383
384
385/**
386 * Determines if a given file path is matched by a config. If the config
387 * has no `files` field, then it matches; otherwise, if a `files` field
388 * is present then we match the globs in `files` and exclude any globs in
389 * `ignores`.
390 * @param {string} filePath The absolute file path to check.
391 * @param {string} basePath The base path for the config.
392 * @param {Object} config The config object to check.
393 * @returns {boolean} True if the file path is matched by the config,
394 * false if not.
395 */
396function pathMatches(filePath, basePath, config) {
397
398 /*
399 * For both files and ignores, functions are passed the absolute
400 * file path while strings are compared against the relative
401 * file path.
402 */
403 const relativeFilePath = path.relative(basePath, filePath);
404
405 // match both strings and functions
406 const match = pattern => {
407
408 if (isString(pattern)) {
409 return doMatch(relativeFilePath, pattern);
410 }
411
412 if (typeof pattern === 'function') {
413 return pattern(filePath);
414 }
415
416 throw new TypeError(`Unexpected matcher type ${pattern}.`);
417 };
418
419 // check for all matches to config.files
420 let filePathMatchesPattern = config.files.some(pattern => {
421 if (Array.isArray(pattern)) {
422 return pattern.every(match);
423 }
424
425 return match(pattern);
426 });
427
428 /*
429 * If the file path matches the config.files patterns, then check to see
430 * if there are any files to ignore.
431 */
432 if (filePathMatchesPattern && config.ignores) {
433 filePathMatchesPattern = !shouldIgnorePath(config.ignores, filePath, relativeFilePath);
434 }
435
436 return filePathMatchesPattern;
437}
438
439/**
440 * Ensures that a ConfigArray has been normalized.
441 * @param {ConfigArray} configArray The ConfigArray to check.
442 * @returns {void}
443 * @throws {Error} When the `ConfigArray` is not normalized.
444 */
445function assertNormalized(configArray) {
446 // TODO: Throw more verbose error
447 if (!configArray.isNormalized()) {
448 throw new Error('ConfigArray must be normalized to perform this operation.');
449 }
450}
451
452/**
453 * Ensures that config types are valid.
454 * @param {Array<string>} extraConfigTypes The config types to check.
455 * @returns {void}
456 * @throws {Error} When the config types array is invalid.
457 */
458function assertExtraConfigTypes(extraConfigTypes) {
459 if (extraConfigTypes.length > 2) {
460 throw new TypeError('configTypes must be an array with at most two items.');
461 }
462
463 for (const configType of extraConfigTypes) {
464 if (!CONFIG_TYPES.has(configType)) {
465 throw new TypeError(`Unexpected config type "${configType}" found. Expected one of: "object", "array", "function".`);
466 }
467 }
468}
469
470//------------------------------------------------------------------------------
471// Public Interface
472//------------------------------------------------------------------------------
473
474const ConfigArraySymbol = {
475 isNormalized: Symbol('isNormalized'),
476 configCache: Symbol('configCache'),
477 schema: Symbol('schema'),
478 finalizeConfig: Symbol('finalizeConfig'),
479 preprocessConfig: Symbol('preprocessConfig')
480};
481
482// used to store calculate data for faster lookup
483const dataCache = new WeakMap();
484
485/**
486 * Represents an array of config objects and provides method for working with
487 * those config objects.
488 */
489class ConfigArray extends Array {
490
491 /**
492 * Creates a new instance of ConfigArray.
493 * @param {Iterable|Function|Object} configs An iterable yielding config
494 * objects, or a config function, or a config object.
495 * @param {string} [options.basePath=""] The path of the config file
496 * @param {boolean} [options.normalized=false] Flag indicating if the
497 * configs have already been normalized.
498 * @param {Object} [options.schema] The additional schema
499 * definitions to use for the ConfigArray schema.
500 * @param {Array<string>} [options.configTypes] List of config types supported.
501 */
502 constructor(configs, {
503 basePath = '',
504 normalized = false,
505 schema: customSchema,
506 extraConfigTypes = []
507 } = {}
508 ) {
509 super();
510
511 /**
512 * Tracks if the array has been normalized.
513 * @property isNormalized
514 * @type boolean
515 * @private
516 */
517 this[ConfigArraySymbol.isNormalized] = normalized;
518
519 /**
520 * The schema used for validating and merging configs.
521 * @property schema
522 * @type ObjectSchema
523 * @private
524 */
525 this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema(
526 Object.assign({}, customSchema, baseSchema)
527 );
528
529 /**
530 * The path of the config file that this array was loaded from.
531 * This is used to calculate filename matches.
532 * @property basePath
533 * @type string
534 */
535 this.basePath = basePath;
536
537 assertExtraConfigTypes(extraConfigTypes);
538
539 /**
540 * The supported config types.
541 * @property configTypes
542 * @type Array<string>
543 */
544 this.extraConfigTypes = Object.freeze([...extraConfigTypes]);
545
546 /**
547 * A cache to store calculated configs for faster repeat lookup.
548 * @property configCache
549 * @type Map
550 * @private
551 */
552 this[ConfigArraySymbol.configCache] = new Map();
553
554 // init cache
555 dataCache.set(this, {
556 explicitMatches: new Map(),
557 directoryMatches: new Map(),
558 files: undefined,
559 ignores: undefined
560 });
561
562 // load the configs into this array
563 if (Array.isArray(configs)) {
564 this.push(...configs);
565 } else {
566 this.push(configs);
567 }
568
569 }
570
571 /**
572 * Prevent normal array methods from creating a new `ConfigArray` instance.
573 * This is to ensure that methods such as `slice()` won't try to create a
574 * new instance of `ConfigArray` behind the scenes as doing so may throw
575 * an error due to the different constructor signature.
576 * @returns {Function} The `Array` constructor.
577 */
578 static get [Symbol.species]() {
579 return Array;
580 }
581
582 /**
583 * Returns the `files` globs from every config object in the array.
584 * This can be used to determine which files will be matched by a
585 * config array or to use as a glob pattern when no patterns are provided
586 * for a command line interface.
587 * @returns {Array<string|Function>} An array of matchers.
588 */
589 get files() {
590
591 assertNormalized(this);
592
593 // if this data has been cached, retrieve it
594 const cache = dataCache.get(this);
595
596 if (cache.files) {
597 return cache.files;
598 }
599
600 // otherwise calculate it
601
602 const result = [];
603
604 for (const config of this) {
605 if (config.files) {
606 config.files.forEach(filePattern => {
607 result.push(filePattern);
608 });
609 }
610 }
611
612 // store result
613 cache.files = result;
614 dataCache.set(this, cache);
615
616 return result;
617 }
618
619 /**
620 * Returns ignore matchers that should always be ignored regardless of
621 * the matching `files` fields in any configs. This is necessary to mimic
622 * the behavior of things like .gitignore and .eslintignore, allowing a
623 * globbing operation to be faster.
624 * @returns {string[]} An array of string patterns and functions to be ignored.
625 */
626 get ignores() {
627
628 assertNormalized(this);
629
630 // if this data has been cached, retrieve it
631 const cache = dataCache.get(this);
632
633 if (cache.ignores) {
634 return cache.ignores;
635 }
636
637 // otherwise calculate it
638
639 const result = [];
640
641 for (const config of this) {
642
643 /*
644 * We only count ignores if there are no other keys in the object.
645 * In this case, it acts list a globally ignored pattern. If there
646 * are additional keys, then ignores act like exclusions.
647 */
648 if (config.ignores && Object.keys(config).length === 1) {
649 result.push(...config.ignores);
650 }
651 }
652
653 // store result
654 cache.ignores = result;
655 dataCache.set(this, cache);
656
657 return result;
658 }
659
660 /**
661 * Indicates if the config array has been normalized.
662 * @returns {boolean} True if the config array is normalized, false if not.
663 */
664 isNormalized() {
665 return this[ConfigArraySymbol.isNormalized];
666 }
667
668 /**
669 * Normalizes a config array by flattening embedded arrays and executing
670 * config functions.
671 * @param {ConfigContext} context The context object for config functions.
672 * @returns {Promise<ConfigArray>} The current ConfigArray instance.
673 */
674 async normalize(context = {}) {
675
676 if (!this.isNormalized()) {
677 const normalizedConfigs = await normalize(this, context, this.extraConfigTypes);
678 this.length = 0;
679 this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this)));
680 this.forEach(assertValidFilesAndIgnores);
681 this[ConfigArraySymbol.isNormalized] = true;
682
683 // prevent further changes
684 Object.freeze(this);
685 }
686
687 return this;
688 }
689
690 /**
691 * Normalizes a config array by flattening embedded arrays and executing
692 * config functions.
693 * @param {ConfigContext} context The context object for config functions.
694 * @returns {ConfigArray} The current ConfigArray instance.
695 */
696 normalizeSync(context = {}) {
697
698 if (!this.isNormalized()) {
699 const normalizedConfigs = normalizeSync(this, context, this.extraConfigTypes);
700 this.length = 0;
701 this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig].bind(this)));
702 this.forEach(assertValidFilesAndIgnores);
703 this[ConfigArraySymbol.isNormalized] = true;
704
705 // prevent further changes
706 Object.freeze(this);
707 }
708
709 return this;
710 }
711
712 /**
713 * Finalizes the state of a config before being cached and returned by
714 * `getConfig()`. Does nothing by default but is provided to be
715 * overridden by subclasses as necessary.
716 * @param {Object} config The config to finalize.
717 * @returns {Object} The finalized config.
718 */
719 [ConfigArraySymbol.finalizeConfig](config) {
720 return config;
721 }
722
723 /**
724 * Preprocesses a config during the normalization process. This is the
725 * method to override if you want to convert an array item before it is
726 * validated for the first time. For example, if you want to replace a
727 * string with an object, this is the method to override.
728 * @param {Object} config The config to preprocess.
729 * @returns {Object} The config to use in place of the argument.
730 */
731 [ConfigArraySymbol.preprocessConfig](config) {
732 return config;
733 }
734
735 /**
736 * Determines if a given file path explicitly matches a `files` entry
737 * and also doesn't match an `ignores` entry. Configs that don't have
738 * a `files` property are not considered an explicit match.
739 * @param {string} filePath The complete path of a file to check.
740 * @returns {boolean} True if the file path matches a `files` entry
741 * or false if not.
742 */
743 isExplicitMatch(filePath) {
744
745 assertNormalized(this);
746
747 const cache = dataCache.get(this);
748
749 // first check the cache to avoid duplicate work
750 let result = cache.explicitMatches.get(filePath);
751
752 if (typeof result == 'boolean') {
753 return result;
754 }
755
756 // TODO: Maybe move elsewhere? Maybe combine with getConfig() logic?
757 const relativeFilePath = path.relative(this.basePath, filePath);
758
759 if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) {
760 debug(`Ignoring ${filePath}`);
761
762 // cache and return result
763 cache.explicitMatches.set(filePath, false);
764 return false;
765 }
766
767 // filePath isn't automatically ignored, so try to find a match
768
769 for (const config of this) {
770
771 if (!config.files) {
772 continue;
773 }
774
775 if (pathMatches(filePath, this.basePath, config)) {
776 debug(`Matching config found for ${filePath}`);
777 cache.explicitMatches.set(filePath, true);
778 return true;
779 }
780 }
781
782 return false;
783 }
784
785 /**
786 * Returns the config object for a given file path.
787 * @param {string} filePath The complete path of a file to get a config for.
788 * @returns {Object} The config object for this file.
789 */
790 getConfig(filePath) {
791
792 assertNormalized(this);
793
794 const cache = this[ConfigArraySymbol.configCache];
795
796 // first check the cache for a filename match to avoid duplicate work
797 if (cache.has(filePath)) {
798 return cache.get(filePath);
799 }
800
801 let finalConfig;
802
803 // next check to see if the file should be ignored
804
805 // check if this should be ignored due to its directory
806 if (this.isDirectoryIgnored(path.dirname(filePath))) {
807 debug(`Ignoring ${filePath} based on directory pattern`);
808
809 // cache and return result - finalConfig is undefined at this point
810 cache.set(filePath, finalConfig);
811 return finalConfig;
812 }
813
814 // TODO: Maybe move elsewhere?
815 const relativeFilePath = path.relative(this.basePath, filePath);
816
817 if (shouldIgnorePath(this.ignores, filePath, relativeFilePath)) {
818 debug(`Ignoring ${filePath} based on file pattern`);
819
820 // cache and return result - finalConfig is undefined at this point
821 cache.set(filePath, finalConfig);
822 return finalConfig;
823 }
824
825 // filePath isn't automatically ignored, so try to construct config
826
827 const matchingConfigIndices = [];
828 let matchFound = false;
829 const universalPattern = /\/\*{1,2}$/;
830
831 this.forEach((config, index) => {
832
833 if (!config.files) {
834
835 if (!config.ignores) {
836 debug(`Anonymous universal config found for ${filePath}`);
837 matchingConfigIndices.push(index);
838 return;
839 }
840
841 if (pathMatchesIgnores(filePath, this.basePath, config)) {
842 debug(`Matching config found for ${filePath} (based on ignores: ${config.ignores})`);
843 matchingConfigIndices.push(index);
844 return;
845 }
846
847 debug(`Skipped config found for ${filePath} (based on ignores: ${config.ignores})`);
848 return;
849 }
850
851 /*
852 * If a config has a files pattern ending in /** or /*, and the
853 * filePath only matches those patterns, then the config is only
854 * applied if there is another config where the filePath matches
855 * a file with a specific extensions such as *.js.
856 */
857
858 const universalFiles = config.files.filter(
859 pattern => universalPattern.test(pattern)
860 );
861
862 // universal patterns were found so we need to check the config twice
863 if (universalFiles.length) {
864
865 debug('Universal files patterns found. Checking carefully.');
866
867 const nonUniversalFiles = config.files.filter(
868 pattern => !universalPattern.test(pattern)
869 );
870
871 // check that the config matches without the non-universal files first
872 if (
873 nonUniversalFiles.length &&
874 pathMatches(
875 filePath, this.basePath,
876 { files: nonUniversalFiles, ignores: config.ignores }
877 )
878 ) {
879 debug(`Matching config found for ${filePath}`);
880 matchingConfigIndices.push(index);
881 matchFound = true;
882 return;
883 }
884
885 // if there wasn't a match then check if it matches with universal files
886 if (
887 universalFiles.length &&
888 pathMatches(
889 filePath, this.basePath,
890 { files: universalFiles, ignores: config.ignores }
891 )
892 ) {
893 debug(`Matching config found for ${filePath}`);
894 matchingConfigIndices.push(index);
895 return;
896 }
897
898 // if we make here, then there was no match
899 return;
900 }
901
902 // the normal case
903 if (pathMatches(filePath, this.basePath, config)) {
904 debug(`Matching config found for ${filePath}`);
905 matchingConfigIndices.push(index);
906 matchFound = true;
907 return;
908 }
909
910 });
911
912 // if matching both files and ignores, there will be no config to create
913 if (!matchFound) {
914 debug(`No matching configs found for ${filePath}`);
915
916 // cache and return result - finalConfig is undefined at this point
917 cache.set(filePath, finalConfig);
918 return finalConfig;
919 }
920
921 // check to see if there is a config cached by indices
922 finalConfig = cache.get(matchingConfigIndices.toString());
923
924 if (finalConfig) {
925
926 // also store for filename for faster lookup next time
927 cache.set(filePath, finalConfig);
928
929 return finalConfig;
930 }
931
932 // otherwise construct the config
933
934 finalConfig = matchingConfigIndices.reduce((result, index) => {
935 return this[ConfigArraySymbol.schema].merge(result, this[index]);
936 }, {}, this);
937
938 finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig);
939
940 cache.set(filePath, finalConfig);
941 cache.set(matchingConfigIndices.toString(), finalConfig);
942
943 return finalConfig;
944 }
945
946 /**
947 * Determines if the given filepath is ignored based on the configs.
948 * @param {string} filePath The complete path of a file to check.
949 * @returns {boolean} True if the path is ignored, false if not.
950 * @deprecated Use `isFileIgnored` instead.
951 */
952 isIgnored(filePath) {
953 return this.isFileIgnored(filePath);
954 }
955
956 /**
957 * Determines if the given filepath is ignored based on the configs.
958 * @param {string} filePath The complete path of a file to check.
959 * @returns {boolean} True if the path is ignored, false if not.
960 */
961 isFileIgnored(filePath) {
962 return this.getConfig(filePath) === undefined;
963 }
964
965 /**
966 * Determines if the given directory is ignored based on the configs.
967 * This checks only default `ignores` that don't have `files` in the
968 * same config. A pattern such as `/foo` be considered to ignore the directory
969 * while a pattern such as `/foo/**` is not considered to ignore the
970 * directory because it is matching files.
971 * @param {string} directoryPath The complete path of a directory to check.
972 * @returns {boolean} True if the directory is ignored, false if not. Will
973 * return true for any directory that is not inside of `basePath`.
974 * @throws {Error} When the `ConfigArray` is not normalized.
975 */
976 isDirectoryIgnored(directoryPath) {
977
978 assertNormalized(this);
979
980 const relativeDirectoryPath = path.relative(this.basePath, directoryPath)
981 .replace(/\\/g, '/');
982
983 if (relativeDirectoryPath.startsWith('..')) {
984 return true;
985 }
986
987 // first check the cache
988 const cache = dataCache.get(this).directoryMatches;
989
990 if (cache.has(relativeDirectoryPath)) {
991 return cache.get(relativeDirectoryPath);
992 }
993
994 const directoryParts = relativeDirectoryPath.split('/');
995 let relativeDirectoryToCheck = '';
996 let result = false;
997
998 /*
999 * In order to get the correct gitignore-style ignores, where an
1000 * ignored parent directory cannot have any descendants unignored,
1001 * we need to check every directory starting at the parent all
1002 * the way down to the actual requested directory.
1003 *
1004 * We aggressively cache all of this info to make sure we don't
1005 * have to recalculate everything for every call.
1006 */
1007 do {
1008
1009 relativeDirectoryToCheck += directoryParts.shift() + '/';
1010
1011 result = shouldIgnorePath(
1012 this.ignores,
1013 path.join(this.basePath, relativeDirectoryToCheck),
1014 relativeDirectoryToCheck
1015 );
1016
1017 cache.set(relativeDirectoryToCheck, result);
1018
1019 } while (!result && directoryParts.length);
1020
1021 // also cache the result for the requested path
1022 cache.set(relativeDirectoryPath, result);
1023
1024 return result;
1025 }
1026
1027}
1028
1029exports.ConfigArray = ConfigArray;
1030exports.ConfigArraySymbol = ConfigArraySymbol;
Note: See TracBrowser for help on using the repository browser.