UNPKG

9.61 kBJavaScriptView Raw
1/**
2 * @fileoverview Create configurations for a rule
3 * @author Ian VanSchooten
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Requirements
10//------------------------------------------------------------------------------
11
12const Rules = require("../rules"),
13 loadRules = require("../load-rules");
14
15const rules = new Rules();
16
17//------------------------------------------------------------------------------
18// Helpers
19//------------------------------------------------------------------------------
20
21/**
22 * Wrap all of the elements of an array into arrays.
23 * @param {*[]} xs Any array.
24 * @returns {Array[]} An array of arrays.
25 */
26function explodeArray(xs) {
27 return xs.reduce((accumulator, x) => {
28 accumulator.push([x]);
29 return accumulator;
30 }, []);
31}
32
33/**
34 * Mix two arrays such that each element of the second array is concatenated
35 * onto each element of the first array.
36 *
37 * For example:
38 * combineArrays([a, [b, c]], [x, y]); // -> [[a, x], [a, y], [b, c, x], [b, c, y]]
39 *
40 * @param {array} arr1 The first array to combine.
41 * @param {array} arr2 The second array to combine.
42 * @returns {array} A mixture of the elements of the first and second arrays.
43 */
44function combineArrays(arr1, arr2) {
45 const res = [];
46
47 if (arr1.length === 0) {
48 return explodeArray(arr2);
49 }
50 if (arr2.length === 0) {
51 return explodeArray(arr1);
52 }
53 arr1.forEach(x1 => {
54 arr2.forEach(x2 => {
55 res.push([].concat(x1, x2));
56 });
57 });
58 return res;
59}
60
61/**
62 * Group together valid rule configurations based on object properties
63 *
64 * e.g.:
65 * groupByProperty([
66 * {before: true},
67 * {before: false},
68 * {after: true},
69 * {after: false}
70 * ]);
71 *
72 * will return:
73 * [
74 * [{before: true}, {before: false}],
75 * [{after: true}, {after: false}]
76 * ]
77 *
78 * @param {Object[]} objects Array of objects, each with one property/value pair
79 * @returns {Array[]} Array of arrays of objects grouped by property
80 */
81function groupByProperty(objects) {
82 const groupedObj = objects.reduce((accumulator, obj) => {
83 const prop = Object.keys(obj)[0];
84
85 accumulator[prop] = accumulator[prop] ? accumulator[prop].concat(obj) : [obj];
86 return accumulator;
87 }, {});
88
89 return Object.keys(groupedObj).map(prop => groupedObj[prop]);
90}
91
92
93//------------------------------------------------------------------------------
94// Private
95//------------------------------------------------------------------------------
96
97/**
98 * Configuration settings for a rule.
99 *
100 * A configuration can be a single number (severity), or an array where the first
101 * element in the array is the severity, and is the only required element.
102 * Configs may also have one or more additional elements to specify rule
103 * configuration or options.
104 *
105 * @typedef {array|number} ruleConfig
106 * @param {number} 0 The rule's severity (0, 1, 2).
107 */
108
109/**
110 * Object whose keys are rule names and values are arrays of valid ruleConfig items
111 * which should be linted against the target source code to determine error counts.
112 * (a ruleConfigSet.ruleConfigs).
113 *
114 * e.g. rulesConfig = {
115 * "comma-dangle": [2, [2, "always"], [2, "always-multiline"], [2, "never"]],
116 * "no-console": [2]
117 * }
118 * @typedef rulesConfig
119 */
120
121
122/**
123 * Create valid rule configurations by combining two arrays,
124 * with each array containing multiple objects each with a
125 * single property/value pair and matching properties.
126 *
127 * e.g.:
128 * combinePropertyObjects(
129 * [{before: true}, {before: false}],
130 * [{after: true}, {after: false}]
131 * );
132 *
133 * will return:
134 * [
135 * {before: true, after: true},
136 * {before: true, after: false},
137 * {before: false, after: true},
138 * {before: false, after: false}
139 * ]
140 *
141 * @param {Object[]} objArr1 Single key/value objects, all with the same key
142 * @param {Object[]} objArr2 Single key/value objects, all with another key
143 * @returns {Object[]} Combined objects for each combination of input properties and values
144 */
145function combinePropertyObjects(objArr1, objArr2) {
146 const res = [];
147
148 if (objArr1.length === 0) {
149 return objArr2;
150 }
151 if (objArr2.length === 0) {
152 return objArr1;
153 }
154 objArr1.forEach(obj1 => {
155 objArr2.forEach(obj2 => {
156 const combinedObj = {};
157 const obj1Props = Object.keys(obj1);
158 const obj2Props = Object.keys(obj2);
159
160 obj1Props.forEach(prop1 => {
161 combinedObj[prop1] = obj1[prop1];
162 });
163 obj2Props.forEach(prop2 => {
164 combinedObj[prop2] = obj2[prop2];
165 });
166 res.push(combinedObj);
167 });
168 });
169 return res;
170}
171
172/**
173 * Creates a new instance of a rule configuration set
174 *
175 * A rule configuration set is an array of configurations that are valid for a
176 * given rule. For example, the configuration set for the "semi" rule could be:
177 *
178 * ruleConfigSet.ruleConfigs // -> [[2], [2, "always"], [2, "never"]]
179 *
180 * Rule configuration set class
181 */
182class RuleConfigSet {
183
184 /**
185 * @param {ruleConfig[]} configs Valid rule configurations
186 */
187 constructor(configs) {
188
189 /**
190 * Stored valid rule configurations for this instance
191 * @type {array}
192 */
193 this.ruleConfigs = configs || [];
194 }
195
196 /**
197 * Add a severity level to the front of all configs in the instance.
198 * This should only be called after all configs have been added to the instance.
199 *
200 * @param {number} [severity=2] The level of severity for the rule (0, 1, 2)
201 * @returns {void}
202 */
203 addErrorSeverity(severity) {
204 severity = severity || 2;
205
206 this.ruleConfigs = this.ruleConfigs.map(config => {
207 config.unshift(severity);
208 return config;
209 });
210
211 // Add a single config at the beginning consisting of only the severity
212 this.ruleConfigs.unshift(severity);
213 }
214
215 /**
216 * Add rule configs from an array of strings (schema enums)
217 * @param {string[]} enums Array of valid rule options (e.g. ["always", "never"])
218 * @returns {void}
219 */
220 addEnums(enums) {
221 this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, enums));
222 }
223
224 /**
225 * Add rule configurations from a schema object
226 * @param {Object} obj Schema item with type === "object"
227 * @returns {boolean} true if at least one schema for the object could be generated, false otherwise
228 */
229 addObject(obj) {
230 const objectConfigSet = {
231 objectConfigs: [],
232 add(property, values) {
233 for (let idx = 0; idx < values.length; idx++) {
234 const optionObj = {};
235
236 optionObj[property] = values[idx];
237 this.objectConfigs.push(optionObj);
238 }
239 },
240
241 combine() {
242 this.objectConfigs = groupByProperty(this.objectConfigs).reduce((accumulator, objArr) => combinePropertyObjects(accumulator, objArr), []);
243 }
244 };
245
246 /*
247 * The object schema could have multiple independent properties.
248 * If any contain enums or booleans, they can be added and then combined
249 */
250 Object.keys(obj.properties).forEach(prop => {
251 if (obj.properties[prop].enum) {
252 objectConfigSet.add(prop, obj.properties[prop].enum);
253 }
254 if (obj.properties[prop].type && obj.properties[prop].type === "boolean") {
255 objectConfigSet.add(prop, [true, false]);
256 }
257 });
258 objectConfigSet.combine();
259
260 if (objectConfigSet.objectConfigs.length > 0) {
261 this.ruleConfigs = this.ruleConfigs.concat(combineArrays(this.ruleConfigs, objectConfigSet.objectConfigs));
262 return true;
263 }
264
265 return false;
266 }
267}
268
269/**
270* Generate valid rule configurations based on a schema object
271* @param {Object} schema A rule's schema object
272* @returns {array[]} Valid rule configurations
273*/
274function generateConfigsFromSchema(schema) {
275 const configSet = new RuleConfigSet();
276
277 if (Array.isArray(schema)) {
278 for (const opt of schema) {
279 if (opt.enum) {
280 configSet.addEnums(opt.enum);
281 } else if (opt.type && opt.type === "object") {
282 if (!configSet.addObject(opt)) {
283 break;
284 }
285
286 // TODO (IanVS): support oneOf
287 } else {
288
289 // If we don't know how to fill in this option, don't fill in any of the following options.
290 break;
291 }
292 }
293 }
294 configSet.addErrorSeverity();
295 return configSet.ruleConfigs;
296}
297
298/**
299* Generate possible rule configurations for all of the core rules
300* @returns {rulesConfig} Hash of rule names and arrays of possible configurations
301*/
302function createCoreRuleConfigs() {
303 const ruleList = loadRules();
304
305 return Object.keys(ruleList).reduce((accumulator, id) => {
306 const rule = rules.get(id);
307 const schema = (typeof rule === "function") ? rule.schema : rule.meta.schema;
308
309 accumulator[id] = generateConfigsFromSchema(schema);
310 return accumulator;
311 }, {});
312}
313
314
315//------------------------------------------------------------------------------
316// Public Interface
317//------------------------------------------------------------------------------
318
319module.exports = {
320 generateConfigsFromSchema,
321 createCoreRuleConfigs
322};