| /** | 
|  * @fileoverview Create configurations for a rule | 
|  * @author Ian VanSchooten | 
|  */ | 
|   | 
| "use strict"; | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Requirements | 
| //------------------------------------------------------------------------------ | 
|   | 
| const builtInRules = require("../rules"); | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Helpers | 
| //------------------------------------------------------------------------------ | 
|   | 
| /** | 
|  * Wrap all of the elements of an array into arrays. | 
|  * @param   {*[]}     xs Any array. | 
|  * @returns {Array[]}    An array of arrays. | 
|  */ | 
| function explodeArray(xs) { | 
|     return xs.reduce((accumulator, x) => { | 
|         accumulator.push([x]); | 
|         return accumulator; | 
|     }, []); | 
| } | 
|   | 
| /** | 
|  * Mix two arrays such that each element of the second array is concatenated | 
|  * onto each element of the first array. | 
|  * | 
|  * For example: | 
|  * combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]] | 
|  * @param   {Array} arr1 The first array to combine. | 
|  * @param   {Array} arr2 The second array to combine. | 
|  * @returns {Array}      A mixture of the elements of the first and second arrays. | 
|  */ | 
| function combineArrays(arr1, arr2) { | 
|     const res = []; | 
|   | 
|     if (arr1.length === 0) { | 
|         return explodeArray(arr2); | 
|     } | 
|     if (arr2.length === 0) { | 
|         return explodeArray(arr1); | 
|     } | 
|     arr1.forEach(x1 => { | 
|         arr2.forEach(x2 => { | 
|             res.push([].concat(x1, x2)); | 
|         }); | 
|     }); | 
|     return res; | 
| } | 
|   | 
| /** | 
|  * Group together valid rule configurations based on object properties | 
|  * | 
|  * e.g.: | 
|  * groupByProperty([ | 
|  *     {before: true}, | 
|  *     {before: false}, | 
|  *     {after: true}, | 
|  *     {after: false} | 
|  * ]); | 
|  * | 
|  * will return: | 
|  * [ | 
|  *     [{before: true}, {before: false}], | 
|  *     [{after: true}, {after: false}] | 
|  * ] | 
|  * @param   {Object[]} objects Array of objects, each with one property/value pair | 
|  * @returns {Array[]}          Array of arrays of objects grouped by property | 
|  */ | 
| function groupByProperty(objects) { | 
|     const groupedObj = objects.reduce((accumulator, obj) => { | 
|         const prop = Object.keys(obj)[0]; | 
|   | 
|         accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj]; | 
|         return accumulator; | 
|     }, {}); | 
|   | 
|     return Object.keys(groupedObj).map(prop => groupedObj[prop]); | 
| } | 
|   | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Private | 
| //------------------------------------------------------------------------------ | 
|   | 
| /** | 
|  * Configuration settings for a rule. | 
|  * | 
|  * A configuration can be a single number (severity), or an array where the first | 
|  * element in the array is the severity, and is the only required element. | 
|  * Configs may also have one or more additional elements to specify rule | 
|  * configuration or options. | 
|  * @typedef {Array|number} ruleConfig | 
|  * @param {number}  0  The rule's severity (0, 1, 2). | 
|  */ | 
|   | 
| /** | 
|  * Object whose keys are rule names and values are arrays of valid ruleConfig items | 
|  * which should be linted against the target source code to determine error counts. | 
|  * (a ruleConfigSet.ruleConfigs). | 
|  * | 
|  * e.g. rulesConfig = { | 
|  *     "comma-dangle": [2, [2, "always"], [2, "always-multiline"], [2, "never"]], | 
|  *     "no-console": [2] | 
|  * } | 
|  * @typedef rulesConfig | 
|  */ | 
|   | 
|   | 
| /** | 
|  * Create valid rule configurations by combining two arrays, | 
|  * with each array containing multiple objects each with a | 
|  * single property/value pair and matching properties. | 
|  * | 
|  * e.g.: | 
|  * combinePropertyObjects( | 
|  *     [{before: true}, {before: false}], | 
|  *     [{after: true}, {after: false}] | 
|  * ); | 
|  * | 
|  * will return: | 
|  * [ | 
|  *     {before: true, after: true}, | 
|  *     {before: true, after: false}, | 
|  *     {before: false, after: true}, | 
|  *     {before: false, after: false} | 
|  * ] | 
|  * @param   {Object[]} objArr1 Single key/value objects, all with the same key | 
|  * @param   {Object[]} objArr2 Single key/value objects, all with another key | 
|  * @returns {Object[]}         Combined objects for each combination of input properties and values | 
|  */ | 
| function combinePropertyObjects(objArr1, objArr2) { | 
|     const res = []; | 
|   | 
|     if (objArr1.length === 0) { | 
|         return objArr2; | 
|     } | 
|     if (objArr2.length === 0) { | 
|         return objArr1; | 
|     } | 
|     objArr1.forEach(obj1 => { | 
|         objArr2.forEach(obj2 => { | 
|             const combinedObj = {}; | 
|             const obj1Props = Object.keys(obj1); | 
|             const obj2Props = Object.keys(obj2); | 
|   | 
|             obj1Props.forEach(prop1 => { | 
|                 combinedObj[prop1] = obj1[prop1]; | 
|             }); | 
|             obj2Props.forEach(prop2 => { | 
|                 combinedObj[prop2] = obj2[prop2]; | 
|             }); | 
|             res.push(combinedObj); | 
|         }); | 
|     }); | 
|     return res; | 
| } | 
|   | 
| /** | 
|  * Creates a new instance of a rule configuration set | 
|  * | 
|  * A rule configuration set is an array of configurations that are valid for a | 
|  * given rule.  For example, the configuration set for the "semi" rule could be: | 
|  * | 
|  * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]] | 
|  * | 
|  * Rule configuration set class | 
|  */ | 
| class RuleConfigSet { | 
|   | 
|     // eslint-disable-next-line jsdoc/require-description | 
|     /** | 
|      * @param {ruleConfig[]} configs Valid rule configurations | 
|      */ | 
|     constructor(configs) { | 
|   | 
|         /** | 
|          * Stored valid rule configurations for this instance | 
|          * @type {Array} | 
|          */ | 
|         this.ruleConfigs = configs || []; | 
|     } | 
|   | 
|     /** | 
|      * Add a severity level to the front of all configs in the instance. | 
|      * This should only be called after all configs have been added to the instance. | 
|      * @returns {void} | 
|      */ | 
|     addErrorSeverity() { | 
|         const severity = 2; | 
|   | 
|         this.ruleConfigs = this.ruleConfigs.map(config => { | 
|             config.unshift(severity); | 
|             return config; | 
|         }); | 
|   | 
|         // Add a single config at the beginning consisting of only the severity | 
|         this.ruleConfigs.unshift(severity); | 
|     } | 
|   | 
|     /** | 
|      * Add rule configs from an array of strings (schema enums) | 
|      * @param  {string[]} enums Array of valid rule options (e.g. ["always", "never"]) | 
|      * @returns {void} | 
|      */ | 
|     addEnums(enums) { | 
|         this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums)); | 
|     } | 
|   | 
|     /** | 
|      * Add rule configurations from a schema object | 
|      * @param  {Object} obj Schema item with type === "object" | 
|      * @returns {boolean} true if at least one schema for the object could be generated, false otherwise | 
|      */ | 
|     addObject(obj) { | 
|         const objectConfigSet = { | 
|             objectConfigs: [], | 
|             add(property, values) { | 
|                 for (let idx = 0; idx < values.length; idx++) { | 
|                     const optionObj = {}; | 
|   | 
|                     optionObj[property] = values[idx]; | 
|                     this.objectConfigs.push(optionObj); | 
|                 } | 
|             }, | 
|   | 
|             combine() { | 
|                 this.objectConfigs = groupByProperty(this.objectConfigs).reduce((accumulator, objArr) => combinePropertyObjects(accumulator, objArr), []); | 
|             } | 
|         }; | 
|   | 
|         /* | 
|          * The object schema could have multiple independent properties. | 
|          * If any contain enums or booleans, they can be added and then combined | 
|          */ | 
|         Object.keys(obj.properties).forEach(prop => { | 
|             if (obj.properties[prop].enum) { | 
|                 objectConfigSet.add(prop, obj.properties[prop].enum); | 
|             } | 
|             if (obj.properties[prop].type && obj.properties[prop].type === "boolean") { | 
|                 objectConfigSet.add(prop, [true, false]); | 
|             } | 
|         }); | 
|         objectConfigSet.combine(); | 
|   | 
|         if (objectConfigSet.objectConfigs.length > 0) { | 
|             this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs)); | 
|             return true; | 
|         } | 
|   | 
|         return false; | 
|     } | 
| } | 
|   | 
| /** | 
|  * Generate valid rule configurations based on a schema object | 
|  * @param   {Object} schema  A rule's schema object | 
|  * @returns {Array[]}        Valid rule configurations | 
|  */ | 
| function generateConfigsFromSchema(schema) { | 
|     const configSet = new RuleConfigSet(); | 
|   | 
|     if (Array.isArray(schema)) { | 
|         for (const opt of schema) { | 
|             if (opt.enum) { | 
|                 configSet.addEnums(opt.enum); | 
|             } else if (opt.type && opt.type === "object") { | 
|                 if (!configSet.addObject(opt)) { | 
|                     break; | 
|                 } | 
|   | 
|             // TODO (IanVS): support oneOf | 
|             } else { | 
|   | 
|                 // If we don't know how to fill in this option, don't fill in any of the following options. | 
|                 break; | 
|             } | 
|         } | 
|     } | 
|     configSet.addErrorSeverity(); | 
|     return configSet.ruleConfigs; | 
| } | 
|   | 
| /** | 
|  * Generate possible rule configurations for all of the core rules | 
|  * @param {boolean} noDeprecated Indicates whether ignores deprecated rules or not. | 
|  * @returns {rulesConfig} Hash of rule names and arrays of possible configurations | 
|  */ | 
| function createCoreRuleConfigs(noDeprecated = false) { | 
|     return Array.from(builtInRules).reduce((accumulator, [id, rule]) => { | 
|         const schema = (typeof rule === "function") ? rule.schema : rule.meta.schema; | 
|         const isDeprecated = (typeof rule === "function") ? rule.deprecated : rule.meta.deprecated; | 
|   | 
|         if (noDeprecated && isDeprecated) { | 
|             return accumulator; | 
|         } | 
|   | 
|         accumulator[id] = generateConfigsFromSchema(schema); | 
|         return accumulator; | 
|     }, {}); | 
| } | 
|   | 
|   | 
| //------------------------------------------------------------------------------ | 
| // Public Interface | 
| //------------------------------------------------------------------------------ | 
|   | 
| module.exports = { | 
|     generateConfigsFromSchema, | 
|     createCoreRuleConfigs | 
| }; |