| /** |
| * @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 { |
| |
| /** |
| * @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 |
| }; |