massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / @humanwhocodes / config-array / api.js
1 'use strict';
2
3 Object.defineProperty(exports, '__esModule', { value: true });
4
5 function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
6
7 var path = _interopDefault(require('path'));
8 var minimatch = _interopDefault(require('minimatch'));
9 var createDebug = _interopDefault(require('debug'));
10 var objectSchema = require('@humanwhocodes/object-schema');
11
12 /**
13  * @fileoverview ConfigSchema
14  * @author Nicholas C. Zakas
15  */
16
17 //------------------------------------------------------------------------------
18 // Helpers
19 //------------------------------------------------------------------------------
20
21 /**
22  * Assets that a given value is an array.
23  * @param {*} value The value to check.
24  * @returns {void}
25  * @throws {TypeError} When the value is not an array. 
26  */
27 function assertIsArray(value) {
28         if (!Array.isArray(value)) {
29                 throw new TypeError('Expected value to be an array.');
30         }
31 }
32
33 /**
34  * Assets that a given value is an array containing only strings and functions.
35  * @param {*} value The value to check.
36  * @returns {void}
37  * @throws {TypeError} When the value is not an array of strings and functions.
38  */
39 function assertIsArrayOfStringsAndFunctions(value, name) {
40         assertIsArray(value);
41
42         if (value.some(item => typeof item !== 'string' && typeof item !== 'function')) {
43                 throw new TypeError('Expected array to only contain strings.');
44         }
45 }
46
47 //------------------------------------------------------------------------------
48 // Exports
49 //------------------------------------------------------------------------------
50
51 /**
52  * The base schema that every ConfigArray uses.
53  * @type Object
54  */
55 const baseSchema = Object.freeze({
56         name: {
57                 required: false,
58                 merge() {
59                         return undefined;
60                 },
61                 validate(value) {
62                         if (typeof value !== 'string') {
63                                 throw new TypeError('Property must be a string.');
64                         }
65                 }
66         },
67         files: {
68                 required: false,
69                 merge() {
70                         return undefined;
71                 },
72                 validate(value) {
73
74                         // first check if it's an array
75                         assertIsArray(value);
76
77                         // then check each member
78                         value.forEach(item => {
79                                 if (Array.isArray(item)) {
80                                         assertIsArrayOfStringsAndFunctions(item);
81                                 } else if (typeof item !== 'string' && typeof item !== 'function') {
82                                         throw new TypeError('Items must be a string, a function, or an array of strings and functions.');
83                                 }
84                         });
85
86                 }
87         },
88         ignores: {
89                 required: false,
90                 merge() {
91                         return undefined;
92                 },
93                 validate: assertIsArrayOfStringsAndFunctions
94         }
95 });
96
97 /**
98  * @fileoverview ConfigArray
99  * @author Nicholas C. Zakas
100  */
101
102 //------------------------------------------------------------------------------
103 // Helpers
104 //------------------------------------------------------------------------------
105
106 const debug = createDebug('@hwc/config-array');
107
108 const MINIMATCH_OPTIONS = {
109         matchBase: true
110 };
111
112 /**
113  * Shorthand for checking if a value is a string.
114  * @param {any} value The value to check.
115  * @returns {boolean} True if a string, false if not. 
116  */
117 function isString(value) {
118         return typeof value === 'string';
119 }
120
121 /**
122  * Normalizes a `ConfigArray` by flattening it and executing any functions
123  * that are found inside.
124  * @param {Array} items The items in a `ConfigArray`.
125  * @param {Object} context The context object to pass into any function
126  *      found.
127  * @returns {Array} A flattened array containing only config objects.
128  * @throws {TypeError} When a config function returns a function.
129  */
130 async function normalize(items, context) {
131
132         // TODO: Allow async config functions
133
134         function *flatTraverse(array) {
135                 for (let item of array) {
136                         if (typeof item === 'function') {
137                                 item = item(context);
138                         }
139
140                         if (Array.isArray(item)) {
141                                 yield * flatTraverse(item);
142                         } else if (typeof item === 'function') {
143                                 throw new TypeError('A config function can only return an object or array.');
144                         } else {
145                                 yield item;
146                         }
147                 }
148         }
149
150         return [...flatTraverse(items)];
151 }
152
153 /**
154  * Determines if a given file path is matched by a config. If the config
155  * has no `files` field, then it matches; otherwise, if a `files` field
156  * is present then we match the globs in `files` and exclude any globs in
157  * `ignores`.
158  * @param {string} filePath The absolute file path to check.
159  * @param {Object} config The config object to check.
160  * @returns {boolean} True if the file path is matched by the config,
161  *      false if not.
162  */
163 function pathMatches(filePath, basePath, config) {
164
165         // a config without a `files` field always matches
166         if (!config.files) {
167                 return true;
168         }
169
170         // if files isn't an array, throw an error
171         if (!Array.isArray(config.files) || config.files.length === 0) {
172                 throw new TypeError('The files key must be a non-empty array.');
173         }
174
175         const relativeFilePath = path.relative(basePath, filePath);
176
177         // match both strings and functions
178         const match = pattern => {
179                 if (isString(pattern)) {
180                         return minimatch(relativeFilePath, pattern, MINIMATCH_OPTIONS);
181                 }
182
183                 if (typeof pattern === 'function') {
184                         return pattern(filePath);
185                 }
186         };
187
188         // check for all matches to config.files
189         let matches = config.files.some(pattern => {
190                 if (Array.isArray(pattern)) {
191                         return pattern.every(match);
192                 }
193
194                 return match(pattern);
195         });
196
197         /*
198          * If the file path matches the config.files patterns, then check to see
199          * if there are any files to ignore.
200          */
201         if (matches && config.ignores) {
202                 matches = !config.ignores.some(pattern => {
203                         return minimatch(filePath, pattern, MINIMATCH_OPTIONS);
204                 });
205         }
206
207         return matches;
208 }
209
210 /**
211  * Ensures that a ConfigArray has been normalized.
212  * @param {ConfigArray} configArray The ConfigArray to check. 
213  * @returns {void}
214  * @throws {Error} When the `ConfigArray` is not normalized.
215  */
216 function assertNormalized(configArray) {
217         // TODO: Throw more verbose error
218         if (!configArray.isNormalized()) {
219                 throw new Error('ConfigArray must be normalized to perform this operation.');
220         }
221 }
222
223 //------------------------------------------------------------------------------
224 // Public Interface
225 //------------------------------------------------------------------------------
226
227 const ConfigArraySymbol = {
228         isNormalized: Symbol('isNormalized'),
229         configCache: Symbol('configCache'),
230         schema: Symbol('schema'),
231         finalizeConfig: Symbol('finalizeConfig'),
232         preprocessConfig: Symbol('preprocessConfig')
233 };
234
235 /**
236  * Represents an array of config objects and provides method for working with
237  * those config objects.
238  */
239 class ConfigArray extends Array {
240
241         /**
242          * Creates a new instance of ConfigArray.
243          * @param {Iterable|Function|Object} configs An iterable yielding config
244          *      objects, or a config function, or a config object.
245          * @param {string} [options.basePath=""] The path of the config file
246          * @param {boolean} [options.normalized=false] Flag indicating if the
247          *      configs have already been normalized.
248          * @param {Object} [options.schema] The additional schema 
249          *      definitions to use for the ConfigArray schema.
250          */
251         constructor(configs, { basePath = '', normalized = false, schema: customSchema } = {}) {
252                 super();
253
254                 /**
255                  * Tracks if the array has been normalized.
256                  * @property isNormalized
257                  * @type boolean
258                  * @private
259                  */
260                 this[ConfigArraySymbol.isNormalized] = normalized;
261
262                 /**
263                  * The schema used for validating and merging configs.
264                  * @property schema
265                  * @type ObjectSchema
266                  * @private
267                  */
268                 this[ConfigArraySymbol.schema] = new objectSchema.ObjectSchema({
269                         ...customSchema,
270                         ...baseSchema
271                 });
272
273                 /**
274                  * The path of the config file that this array was loaded from.
275                  * This is used to calculate filename matches.
276                  * @property basePath
277                  * @type string
278                  */
279                 this.basePath = basePath;
280
281                 /**
282                  * A cache to store calculated configs for faster repeat lookup.
283                  * @property configCache
284                  * @type Map
285                  * @private
286                  */
287                 this[ConfigArraySymbol.configCache] = new Map();
288
289                 // load the configs into this array
290                 if (Array.isArray(configs)) {
291                         this.push(...configs);
292                 } else {
293                         this.push(configs);
294                 }
295
296         }
297
298         /**
299          * Prevent normal array methods from creating a new `ConfigArray` instance.
300          * This is to ensure that methods such as `slice()` won't try to create a 
301          * new instance of `ConfigArray` behind the scenes as doing so may throw
302          * an error due to the different constructor signature.
303          * @returns {Function} The `Array` constructor.
304          */
305         static get [Symbol.species]() {
306                 return Array;
307         }
308
309         /**
310          * Returns the `files` globs from every config object in the array.
311          * Negated patterns (those beginning with `!`) are not returned.
312          * This can be used to determine which files will be matched by a
313          * config array or to use as a glob pattern when no patterns are provided
314          * for a command line interface.
315          * @returns {string[]} An array of string patterns.
316          */
317         get files() {
318
319                 assertNormalized(this);
320
321                 const result = [];
322
323                 for (const config of this) {
324                         if (config.files) {
325                                 config.files.forEach(filePattern => {
326                                         if (Array.isArray(filePattern)) {
327                                                 result.push(...filePattern.filter(pattern => {
328                                                         return isString(pattern) && !pattern.startsWith('!');
329                                                 }));
330                                         } else if (isString(filePattern) && !filePattern.startsWith('!')) {
331                                                 result.push(filePattern);
332                                         }
333                                 });
334                         }
335                 }
336
337                 return result;
338         }
339
340         /**
341          * Returns the file globs that should always be ignored regardless of
342          * the matching `files` fields in any configs. This is necessary to mimic
343          * the behavior of things like .gitignore and .eslintignore, allowing a
344          * globbing operation to be faster.
345          * @returns {string[]} An array of string patterns to be ignored.
346          */
347         get ignores() {
348
349                 assertNormalized(this);
350
351                 const result = [];
352
353                 for (const config of this) {
354                         if (config.ignores && !config.files) {
355                                 result.push(...config.ignores.filter(isString));
356                         }
357                 }
358
359                 return result;
360         }
361
362         /**
363          * Indicates if the config array has been normalized.
364          * @returns {boolean} True if the config array is normalized, false if not.
365          */
366         isNormalized() {
367                 return this[ConfigArraySymbol.isNormalized];
368         }
369
370         /**
371          * Normalizes a config array by flattening embedded arrays and executing
372          * config functions.
373          * @param {ConfigContext} context The context object for config functions.
374          * @returns {ConfigArray} A new ConfigArray instance that is normalized.
375          */
376         async normalize(context = {}) {
377
378                 if (!this.isNormalized()) {
379                         const normalizedConfigs = await normalize(this, context);
380                         this.length = 0;
381                         this.push(...normalizedConfigs.map(this[ConfigArraySymbol.preprocessConfig]));
382                         this[ConfigArraySymbol.isNormalized] = true;
383
384                         // prevent further changes
385                         Object.freeze(this);
386                 }
387
388                 return this;
389         }
390
391         /**
392          * Finalizes the state of a config before being cached and returned by
393          * `getConfig()`. Does nothing by default but is provided to be
394          * overridden by subclasses as necessary.
395          * @param {Object} config The config to finalize.
396          * @returns {Object} The finalized config.
397          */
398         [ConfigArraySymbol.finalizeConfig](config) {
399                 return config;
400         }
401
402         /**
403          * Preprocesses a config during the normalization process. This is the
404          * method to override if you want to convert an array item before it is
405          * validated for the first time. For example, if you want to replace a
406          * string with an object, this is the method to override.
407          * @param {Object} config The config to preprocess.
408          * @returns {Object} The config to use in place of the argument.
409          */
410         [ConfigArraySymbol.preprocessConfig](config) {
411                 return config;
412         }
413
414         /**
415          * Returns the config object for a given file path.
416          * @param {string} filePath The complete path of a file to get a config for.
417          * @returns {Object} The config object for this file.
418          */
419         getConfig(filePath) {
420
421                 assertNormalized(this);
422
423                 // first check the cache to avoid duplicate work
424                 let finalConfig = this[ConfigArraySymbol.configCache].get(filePath);
425
426                 if (finalConfig) {
427                         return finalConfig;
428                 }
429
430                 // No config found in cache, so calculate a new one
431
432                 const matchingConfigs = [];
433
434                 for (const config of this) {
435                         if (pathMatches(filePath, this.basePath, config)) {
436                                 debug(`Matching config found for ${filePath}`);
437                                 matchingConfigs.push(config);
438                         } else {
439                                 debug(`No matching config found for ${filePath}`);
440                         }
441                 }
442
443                 finalConfig = matchingConfigs.reduce((result, config) => {
444                         return this[ConfigArraySymbol.schema].merge(result, config);
445                 }, {}, this);
446
447                 finalConfig = this[ConfigArraySymbol.finalizeConfig](finalConfig);
448
449                 this[ConfigArraySymbol.configCache].set(filePath, finalConfig);
450
451                 return finalConfig;
452         }
453
454 }
455
456 exports.ConfigArray = ConfigArray;
457 exports.ConfigArraySymbol = ConfigArraySymbol;