2 * @fileoverview Create configurations for a rule
3 * @author Ian VanSchooten
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const builtInRules
= require("../rules");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
19 * Wrap all of the elements of an array into arrays.
20 * @param {*[]} xs Any array.
21 * @returns {Array[]} An array of arrays.
23 function explodeArray(xs
) {
24 return xs
.reduce((accumulator
, x
) => {
25 accumulator
.push([x
]);
31 * Mix two arrays such that each element of the second array is concatenated
32 * onto each element of the first array.
35 * combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]]
36 * @param {Array} arr1 The first array to combine.
37 * @param {Array} arr2 The second array to combine.
38 * @returns {Array} A mixture of the elements of the first and second arrays.
40 function combineArrays(arr1
, arr2
) {
43 if (arr1
.length
=== 0) {
44 return explodeArray(arr2
);
46 if (arr2
.length
=== 0) {
47 return explodeArray(arr1
);
51 res
.push([].concat(x1
, x2
));
58 * Group together valid rule configurations based on object properties
70 * [{before: true}, {before: false}],
71 * [{after: true}, {after: false}]
73 * @param {Object[]} objects Array of objects, each with one property/value pair
74 * @returns {Array[]} Array of arrays of objects grouped by property
76 function groupByProperty(objects
) {
77 const groupedObj
= objects
.reduce((accumulator
, obj
) => {
78 const prop
= Object
.keys(obj
)[0];
80 accumulator
[prop
] = accumulator
[prop
] ? accumulator
[prop
].concat(obj
) : [obj
];
84 return Object
.keys(groupedObj
).map(prop
=> groupedObj
[prop
]);
88 //------------------------------------------------------------------------------
90 //------------------------------------------------------------------------------
93 * Configuration settings for a rule.
95 * A configuration can be a single number (severity), or an array where the first
96 * element in the array is the severity, and is the only required element.
97 * Configs may also have one or more additional elements to specify rule
98 * configuration or options.
99 * @typedef {Array|number} ruleConfig
100 * @param {number} 0 The rule's severity (0, 1, 2).
104 * Object whose keys are rule names and values are arrays of valid ruleConfig items
105 * which should be linted against the target source code to determine error counts.
106 * (a ruleConfigSet.ruleConfigs).
108 * e.g. rulesConfig = {
109 * "comma-dangle": [2, [2, "always"], [2, "always-multiline"], [2, "never"]],
112 * @typedef rulesConfig
117 * Create valid rule configurations by combining two arrays,
118 * with each array containing multiple objects each with a
119 * single property/value pair and matching properties.
122 * combinePropertyObjects(
123 * [{before: true}, {before: false}],
124 * [{after: true}, {after: false}]
129 * {before: true, after: true},
130 * {before: true, after: false},
131 * {before: false, after: true},
132 * {before: false, after: false}
134 * @param {Object[]} objArr1 Single key/value objects, all with the same key
135 * @param {Object[]} objArr2 Single key/value objects, all with another key
136 * @returns {Object[]} Combined objects for each combination of input properties and values
138 function combinePropertyObjects(objArr1
, objArr2
) {
141 if (objArr1
.length
=== 0) {
144 if (objArr2
.length
=== 0) {
147 objArr1
.forEach(obj1
=> {
148 objArr2
.forEach(obj2
=> {
149 const combinedObj
= {};
150 const obj1Props
= Object
.keys(obj1
);
151 const obj2Props
= Object
.keys(obj2
);
153 obj1Props
.forEach(prop1
=> {
154 combinedObj
[prop1
] = obj1
[prop1
];
156 obj2Props
.forEach(prop2
=> {
157 combinedObj
[prop2
] = obj2
[prop2
];
159 res
.push(combinedObj
);
166 * Creates a new instance of a rule configuration set
168 * A rule configuration set is an array of configurations that are valid for a
169 * given rule. For example, the configuration set for the "semi" rule could be:
171 * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
173 * Rule configuration set class
175 class RuleConfigSet
{
177 // eslint-disable-next-line jsdoc/require-description
179 * @param {ruleConfig[]} configs Valid rule configurations
181 constructor(configs
) {
184 * Stored valid rule configurations for this instance
187 this.ruleConfigs
= configs
|| [];
191 * Add a severity level to the front of all configs in the instance.
192 * This should only be called after all configs have been added to the instance.
198 this.ruleConfigs
= this.ruleConfigs
.map(config
=> {
199 config
.unshift(severity
);
203 // Add a single config at the beginning consisting of only the severity
204 this.ruleConfigs
.unshift(severity
);
208 * Add rule configs from an array of strings (schema enums)
209 * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"])
213 this.ruleConfigs
= this.ruleConfigs
.concat(combineArrays(this.ruleConfigs
, enums
));
217 * Add rule configurations from a schema object
218 * @param {Object} obj Schema item with type === "object"
219 * @returns {boolean} true if at least one schema for the object could be generated, false otherwise
222 const objectConfigSet
= {
224 add(property
, values
) {
225 for (let idx
= 0; idx
< values
.length
; idx
++) {
226 const optionObj
= {};
228 optionObj
[property
] = values
[idx
];
229 this.objectConfigs
.push(optionObj
);
234 this.objectConfigs
= groupByProperty(this.objectConfigs
).reduce((accumulator
, objArr
) => combinePropertyObjects(accumulator
, objArr
), []);
239 * The object schema could have multiple independent properties.
240 * If any contain enums or booleans, they can be added and then combined
242 Object
.keys(obj
.properties
).forEach(prop
=> {
243 if (obj
.properties
[prop
].enum) {
244 objectConfigSet
.add(prop
, obj
.properties
[prop
].enum);
246 if (obj
.properties
[prop
].type
&& obj
.properties
[prop
].type
=== "boolean") {
247 objectConfigSet
.add(prop
, [true, false]);
250 objectConfigSet
.combine();
252 if (objectConfigSet
.objectConfigs
.length
> 0) {
253 this.ruleConfigs
= this.ruleConfigs
.concat(combineArrays(this.ruleConfigs
, objectConfigSet
.objectConfigs
));
262 * Generate valid rule configurations based on a schema object
263 * @param {Object} schema A rule's schema object
264 * @returns {Array[]} Valid rule configurations
266 function generateConfigsFromSchema(schema
) {
267 const configSet
= new RuleConfigSet();
269 if (Array
.isArray(schema
)) {
270 for (const opt
of schema
) {
272 configSet
.addEnums(opt
.enum);
273 } else if (opt
.type
&& opt
.type
=== "object") {
274 if (!configSet
.addObject(opt
)) {
278 // TODO (IanVS): support oneOf
281 // If we don't know how to fill in this option, don't fill in any of the following options.
286 configSet
.addErrorSeverity();
287 return configSet
.ruleConfigs
;
291 * Generate possible rule configurations for all of the core rules
292 * @param {boolean} noDeprecated Indicates whether ignores deprecated rules or not.
293 * @returns {rulesConfig} Hash of rule names and arrays of possible configurations
295 function createCoreRuleConfigs(noDeprecated
= false) {
296 return Array
.from(builtInRules
).reduce((accumulator
, [id
, rule
]) => {
297 const schema
= (typeof rule
=== "function") ? rule
.schema
: rule
.meta
.schema
;
298 const isDeprecated
= (typeof rule
=== "function") ? rule
.deprecated
: rule
.meta
.deprecated
;
300 if (noDeprecated
&& isDeprecated
) {
304 accumulator
[id
] = generateConfigsFromSchema(schema
);
310 //------------------------------------------------------------------------------
312 //------------------------------------------------------------------------------
315 generateConfigsFromSchema
,
316 createCoreRuleConfigs