massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / init / autoconfig.js
1 /**
2  * @fileoverview Used for creating a suggested configuration based on project code.
3  * @author Ian VanSchooten
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const equal = require("fast-deep-equal"),
13     recConfig = require("../../conf/eslint-recommended"),
14     ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops"),
15     { Linter } = require("../linter"),
16     configRule = require("./config-rule");
17
18 const debug = require("debug")("eslint:autoconfig");
19 const linter = new Linter();
20
21 //------------------------------------------------------------------------------
22 // Data
23 //------------------------------------------------------------------------------
24
25 const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only
26     RECOMMENDED_CONFIG_NAME = "eslint:recommended";
27
28 //------------------------------------------------------------------------------
29 // Private
30 //------------------------------------------------------------------------------
31
32 /**
33  * Information about a rule configuration, in the context of a Registry.
34  * @typedef {Object}     registryItem
35  * @param   {ruleConfig} config        A valid configuration for the rule
36  * @param   {number}     specificity   The number of elements in the ruleConfig array
37  * @param   {number}     errorCount    The number of errors encountered when linting with the config
38  */
39
40 /**
41  * This callback is used to measure execution status in a progress bar
42  * @callback progressCallback
43  * @param {number} The total number of times the callback will be called.
44  */
45
46 /**
47  * Create registryItems for rules
48  * @param   {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items
49  * @returns {Object}                  registryItems for each rule in provided rulesConfig
50  */
51 function makeRegistryItems(rulesConfig) {
52     return Object.keys(rulesConfig).reduce((accumulator, ruleId) => {
53         accumulator[ruleId] = rulesConfig[ruleId].map(config => ({
54             config,
55             specificity: config.length || 1,
56             errorCount: void 0
57         }));
58         return accumulator;
59     }, {});
60 }
61
62 /**
63  * Creates an object in which to store rule configs and error counts
64  *
65  * Unless a rulesConfig is provided at construction, the registry will not contain
66  * any rules, only methods.  This will be useful for building up registries manually.
67  *
68  * Registry class
69  */
70 class Registry {
71
72     // eslint-disable-next-line jsdoc/require-description
73     /**
74      * @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
75      */
76     constructor(rulesConfig) {
77         this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};
78     }
79
80     /**
81      * Populate the registry with core rule configs.
82      *
83      * It will set the registry's `rule` property to an object having rule names
84      * as keys and an array of registryItems as values.
85      * @returns {void}
86      */
87     populateFromCoreRules() {
88         const rulesConfig = configRule.createCoreRuleConfigs(/* noDeprecated = */ true);
89
90         this.rules = makeRegistryItems(rulesConfig);
91     }
92
93     /**
94      * Creates sets of rule configurations which can be used for linting
95      * and initializes registry errors to zero for those configurations (side effect).
96      *
97      * This combines as many rules together as possible, such that the first sets
98      * in the array will have the highest number of rules configured, and later sets
99      * will have fewer and fewer, as not all rules have the same number of possible
100      * configurations.
101      *
102      * The length of the returned array will be <= MAX_CONFIG_COMBINATIONS.
103      * @returns {Object[]}          "rules" configurations to use for linting
104      */
105     buildRuleSets() {
106         let idx = 0;
107         const ruleIds = Object.keys(this.rules),
108             ruleSets = [];
109
110         /**
111          * Add a rule configuration from the registry to the ruleSets
112          *
113          * This is broken out into its own function so that it doesn't need to be
114          * created inside of the while loop.
115          * @param   {string} rule The ruleId to add.
116          * @returns {void}
117          */
118         const addRuleToRuleSet = function(rule) {
119
120             /*
121              * This check ensures that there is a rule configuration and that
122              * it has fewer than the max combinations allowed.
123              * If it has too many configs, we will only use the most basic of
124              * the possible configurations.
125              */
126             const hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS);
127
128             if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) {
129
130                 /*
131                  * If the rule has too many possible combinations, only take
132                  * simple ones, avoiding objects.
133                  */
134                 if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") {
135                     return;
136                 }
137
138                 ruleSets[idx] = ruleSets[idx] || {};
139                 ruleSets[idx][rule] = this.rules[rule][idx].config;
140
141                 /*
142                  * Initialize errorCount to zero, since this is a config which
143                  * will be linted.
144                  */
145                 this.rules[rule][idx].errorCount = 0;
146             }
147         }.bind(this);
148
149         while (ruleSets.length === idx) {
150             ruleIds.forEach(addRuleToRuleSet);
151             idx += 1;
152         }
153
154         return ruleSets;
155     }
156
157     /**
158      * Remove all items from the registry with a non-zero number of errors
159      *
160      * Note: this also removes rule configurations which were not linted
161      * (meaning, they have an undefined errorCount).
162      * @returns {void}
163      */
164     stripFailingConfigs() {
165         const ruleIds = Object.keys(this.rules),
166             newRegistry = new Registry();
167
168         newRegistry.rules = Object.assign({}, this.rules);
169         ruleIds.forEach(ruleId => {
170             const errorFreeItems = newRegistry.rules[ruleId].filter(registryItem => (registryItem.errorCount === 0));
171
172             if (errorFreeItems.length > 0) {
173                 newRegistry.rules[ruleId] = errorFreeItems;
174             } else {
175                 delete newRegistry.rules[ruleId];
176             }
177         });
178
179         return newRegistry;
180     }
181
182     /**
183      * Removes rule configurations which were not included in a ruleSet
184      * @returns {void}
185      */
186     stripExtraConfigs() {
187         const ruleIds = Object.keys(this.rules),
188             newRegistry = new Registry();
189
190         newRegistry.rules = Object.assign({}, this.rules);
191         ruleIds.forEach(ruleId => {
192             newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(registryItem => (typeof registryItem.errorCount !== "undefined"));
193         });
194
195         return newRegistry;
196     }
197
198     /**
199      * Creates a registry of rules which had no error-free configs.
200      * The new registry is intended to be analyzed to determine whether its rules
201      * should be disabled or set to warning.
202      * @returns {Registry}  A registry of failing rules.
203      */
204     getFailingRulesRegistry() {
205         const ruleIds = Object.keys(this.rules),
206             failingRegistry = new Registry();
207
208         ruleIds.forEach(ruleId => {
209             const failingConfigs = this.rules[ruleId].filter(registryItem => (registryItem.errorCount > 0));
210
211             if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {
212                 failingRegistry.rules[ruleId] = failingConfigs;
213             }
214         });
215
216         return failingRegistry;
217     }
218
219     /**
220      * Create an eslint config for any rules which only have one configuration
221      * in the registry.
222      * @returns {Object} An eslint config with rules section populated
223      */
224     createConfig() {
225         const ruleIds = Object.keys(this.rules),
226             config = { rules: {} };
227
228         ruleIds.forEach(ruleId => {
229             if (this.rules[ruleId].length === 1) {
230                 config.rules[ruleId] = this.rules[ruleId][0].config;
231             }
232         });
233
234         return config;
235     }
236
237     /**
238      * Return a cloned registry containing only configs with a desired specificity
239      * @param   {number} specificity Only keep configs with this specificity
240      * @returns {Registry}           A registry of rules
241      */
242     filterBySpecificity(specificity) {
243         const ruleIds = Object.keys(this.rules),
244             newRegistry = new Registry();
245
246         newRegistry.rules = Object.assign({}, this.rules);
247         ruleIds.forEach(ruleId => {
248             newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity));
249         });
250
251         return newRegistry;
252     }
253
254     /**
255      * Lint SourceCodes against all configurations in the registry, and record results
256      * @param   {Object[]} sourceCodes  SourceCode objects for each filename
257      * @param   {Object}   config       ESLint config object
258      * @param   {progressCallback} [cb] Optional callback for reporting execution status
259      * @returns {Registry}              New registry with errorCount populated
260      */
261     lintSourceCode(sourceCodes, config, cb) {
262         let lintedRegistry = new Registry();
263
264         lintedRegistry.rules = Object.assign({}, this.rules);
265
266         const ruleSets = lintedRegistry.buildRuleSets();
267
268         lintedRegistry = lintedRegistry.stripExtraConfigs();
269
270         debug("Linting with all possible rule combinations");
271
272         const filenames = Object.keys(sourceCodes);
273         const totalFilesLinting = filenames.length * ruleSets.length;
274
275         filenames.forEach(filename => {
276             debug(`Linting file: ${filename}`);
277
278             let ruleSetIdx = 0;
279
280             ruleSets.forEach(ruleSet => {
281                 const lintConfig = Object.assign({}, config, { rules: ruleSet });
282                 const lintResults = linter.verify(sourceCodes[filename], lintConfig);
283
284                 lintResults.forEach(result => {
285
286                     /*
287                      * It is possible that the error is from a configuration comment
288                      * in a linted file, in which case there may not be a config
289                      * set in this ruleSetIdx.
290                      * (https://github.com/eslint/eslint/issues/5992)
291                      * (https://github.com/eslint/eslint/issues/7860)
292                      */
293                     if (
294                         lintedRegistry.rules[result.ruleId] &&
295                         lintedRegistry.rules[result.ruleId][ruleSetIdx]
296                     ) {
297                         lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;
298                     }
299                 });
300
301                 ruleSetIdx += 1;
302
303                 if (cb) {
304                     cb(totalFilesLinting); // eslint-disable-line node/callback-return
305                 }
306             });
307
308             // Deallocate for GC
309             sourceCodes[filename] = null;
310         });
311
312         return lintedRegistry;
313     }
314 }
315
316 /**
317  * Extract rule configuration into eslint:recommended where possible.
318  *
319  * This will return a new config with `["extends": [ ..., "eslint:recommended"]` and
320  * only the rules which have configurations different from the recommended config.
321  * @param   {Object} config config object
322  * @returns {Object}        config object using `"extends": ["eslint:recommended"]`
323  */
324 function extendFromRecommended(config) {
325     const newConfig = Object.assign({}, config);
326
327     ConfigOps.normalizeToStrings(newConfig);
328
329     const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));
330
331     recRules.forEach(ruleId => {
332         if (equal(recConfig.rules[ruleId], newConfig.rules[ruleId])) {
333             delete newConfig.rules[ruleId];
334         }
335     });
336     newConfig.extends.unshift(RECOMMENDED_CONFIG_NAME);
337     return newConfig;
338 }
339
340
341 //------------------------------------------------------------------------------
342 // Public Interface
343 //------------------------------------------------------------------------------
344
345 module.exports = {
346     Registry,
347     extendFromRecommended
348 };