]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/init/autoconfig.js
2 * @fileoverview Used for creating a suggested configuration based on project code.
3 * @author Ian VanSchooten
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
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");
18 const debug
= require("debug")("eslint:autoconfig");
19 const linter
= new Linter();
21 //------------------------------------------------------------------------------
23 //------------------------------------------------------------------------------
25 const MAX_CONFIG_COMBINATIONS
= 17, // 16 combinations + 1 for severity only
26 RECOMMENDED_CONFIG_NAME
= "eslint:recommended";
28 //------------------------------------------------------------------------------
30 //------------------------------------------------------------------------------
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
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.
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
51 function makeRegistryItems(rulesConfig
) {
52 return Object
.keys(rulesConfig
).reduce((accumulator
, ruleId
) => {
53 accumulator
[ruleId
] = rulesConfig
[ruleId
].map(config
=> ({
55 specificity
: config
.length
|| 1,
63 * Creates an object in which to store rule configs and error counts
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.
72 // eslint-disable-next-line jsdoc/require-description
74 * @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
76 constructor(rulesConfig
) {
77 this.rules
= (rulesConfig
) ? makeRegistryItems(rulesConfig
) : {};
81 * Populate the registry with core rule configs.
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.
87 populateFromCoreRules() {
88 const rulesConfig
= configRule
.createCoreRuleConfigs(/* noDeprecated = */ true);
90 this.rules
= makeRegistryItems(rulesConfig
);
94 * Creates sets of rule configurations which can be used for linting
95 * and initializes registry errors to zero for those configurations (side effect).
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
102 * The length of the returned array will be <= MAX_CONFIG_COMBINATIONS.
103 * @returns {Object[]} "rules" configurations to use for linting
107 const ruleIds
= Object
.keys(this.rules
),
111 * Add a rule configuration from the registry to the ruleSets
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.
118 const addRuleToRuleSet = function(rule
) {
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.
126 const hasFewCombos
= (this.rules
[rule
].length
<= MAX_CONFIG_COMBINATIONS
);
128 if (this.rules
[rule
][idx
] && (hasFewCombos
|| this.rules
[rule
][idx
].specificity
<= 2)) {
131 * If the rule has too many possible combinations, only take
132 * simple ones, avoiding objects.
134 if (!hasFewCombos
&& typeof this.rules
[rule
][idx
].config
[1] === "object") {
138 ruleSets
[idx
] = ruleSets
[idx
] || {};
139 ruleSets
[idx
][rule
] = this.rules
[rule
][idx
].config
;
142 * Initialize errorCount to zero, since this is a config which
145 this.rules
[rule
][idx
].errorCount
= 0;
149 while (ruleSets
.length
=== idx
) {
150 ruleIds
.forEach(addRuleToRuleSet
);
158 * Remove all items from the registry with a non-zero number of errors
160 * Note: this also removes rule configurations which were not linted
161 * (meaning, they have an undefined errorCount).
164 stripFailingConfigs() {
165 const ruleIds
= Object
.keys(this.rules
),
166 newRegistry
= new Registry();
168 newRegistry
.rules
= Object
.assign({}, this.rules
);
169 ruleIds
.forEach(ruleId
=> {
170 const errorFreeItems
= newRegistry
.rules
[ruleId
].filter(registryItem
=> (registryItem
.errorCount
=== 0));
172 if (errorFreeItems
.length
> 0) {
173 newRegistry
.rules
[ruleId
] = errorFreeItems
;
175 delete newRegistry
.rules
[ruleId
];
183 * Removes rule configurations which were not included in a ruleSet
186 stripExtraConfigs() {
187 const ruleIds
= Object
.keys(this.rules
),
188 newRegistry
= new Registry();
190 newRegistry
.rules
= Object
.assign({}, this.rules
);
191 ruleIds
.forEach(ruleId
=> {
192 newRegistry
.rules
[ruleId
] = newRegistry
.rules
[ruleId
].filter(registryItem
=> (typeof registryItem
.errorCount
!== "undefined"));
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.
204 getFailingRulesRegistry() {
205 const ruleIds
= Object
.keys(this.rules
),
206 failingRegistry
= new Registry();
208 ruleIds
.forEach(ruleId
=> {
209 const failingConfigs
= this.rules
[ruleId
].filter(registryItem
=> (registryItem
.errorCount
> 0));
211 if (failingConfigs
&& failingConfigs
.length
=== this.rules
[ruleId
].length
) {
212 failingRegistry
.rules
[ruleId
] = failingConfigs
;
216 return failingRegistry
;
220 * Create an eslint config for any rules which only have one configuration
222 * @returns {Object} An eslint config with rules section populated
225 const ruleIds
= Object
.keys(this.rules
),
226 config
= { rules
: {} };
228 ruleIds
.forEach(ruleId
=> {
229 if (this.rules
[ruleId
].length
=== 1) {
230 config
.rules
[ruleId
] = this.rules
[ruleId
][0].config
;
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
242 filterBySpecificity(specificity
) {
243 const ruleIds
= Object
.keys(this.rules
),
244 newRegistry
= new Registry();
246 newRegistry
.rules
= Object
.assign({}, this.rules
);
247 ruleIds
.forEach(ruleId
=> {
248 newRegistry
.rules
[ruleId
] = this.rules
[ruleId
].filter(registryItem
=> (registryItem
.specificity
=== specificity
));
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
261 lintSourceCode(sourceCodes
, config
, cb
) {
262 let lintedRegistry
= new Registry();
264 lintedRegistry
.rules
= Object
.assign({}, this.rules
);
266 const ruleSets
= lintedRegistry
.buildRuleSets();
268 lintedRegistry
= lintedRegistry
.stripExtraConfigs();
270 debug("Linting with all possible rule combinations");
272 const filenames
= Object
.keys(sourceCodes
);
273 const totalFilesLinting
= filenames
.length
* ruleSets
.length
;
275 filenames
.forEach(filename
=> {
276 debug(`Linting file: ${filename}`);
280 ruleSets
.forEach(ruleSet
=> {
281 const lintConfig
= Object
.assign({}, config
, { rules
: ruleSet
});
282 const lintResults
= linter
.verify(sourceCodes
[filename
], lintConfig
);
284 lintResults
.forEach(result
=> {
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)
294 lintedRegistry
.rules
[result
.ruleId
] &&
295 lintedRegistry
.rules
[result
.ruleId
][ruleSetIdx
]
297 lintedRegistry
.rules
[result
.ruleId
][ruleSetIdx
].errorCount
+= 1;
304 cb(totalFilesLinting
); // eslint-disable-line node/callback-return
309 sourceCodes
[filename
] = null;
312 return lintedRegistry
;
317 * Extract rule configuration into eslint:recommended where possible.
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"]`
324 function extendFromRecommended(config
) {
325 const newConfig
= Object
.assign({}, config
);
327 ConfigOps
.normalizeToStrings(newConfig
);
329 const recRules
= Object
.keys(recConfig
.rules
).filter(ruleId
=> ConfigOps
.isErrorSeverity(recConfig
.rules
[ruleId
]));
331 recRules
.forEach(ruleId
=> {
332 if (equal(recConfig
.rules
[ruleId
], newConfig
.rules
[ruleId
])) {
333 delete newConfig
.rules
[ruleId
];
336 newConfig
.extends.unshift(RECOMMENDED_CONFIG_NAME
);
341 //------------------------------------------------------------------------------
343 //------------------------------------------------------------------------------
347 extendFromRecommended