]>
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"),
18 } = require("@eslint/eslintrc"),
19 { Linter
} = require("../linter"),
20 configRule
= require("./config-rule");
22 const debug
= require("debug")("eslint:autoconfig");
23 const linter
= new Linter();
25 //------------------------------------------------------------------------------
27 //------------------------------------------------------------------------------
29 const MAX_CONFIG_COMBINATIONS
= 17, // 16 combinations + 1 for severity only
30 RECOMMENDED_CONFIG_NAME
= "eslint:recommended";
32 //------------------------------------------------------------------------------
34 //------------------------------------------------------------------------------
37 * Information about a rule configuration, in the context of a Registry.
38 * @typedef {Object} registryItem
39 * @property {ruleConfig} config A valid configuration for the rule
40 * @property {number} specificity The number of elements in the ruleConfig array
41 * @property {number} errorCount The number of errors encountered when linting with the config
45 * This callback is used to measure execution status in a progress bar
46 * @callback progressCallback
47 * @param {number} The total number of times the callback will be called.
51 * Create registryItems for rules
52 * @param {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items
53 * @returns {Object} registryItems for each rule in provided rulesConfig
55 function makeRegistryItems(rulesConfig
) {
56 return Object
.keys(rulesConfig
).reduce((accumulator
, ruleId
) => {
57 accumulator
[ruleId
] = rulesConfig
[ruleId
].map(config
=> ({
59 specificity
: config
.length
|| 1,
67 * Creates an object in which to store rule configs and error counts
69 * Unless a rulesConfig is provided at construction, the registry will not contain
70 * any rules, only methods. This will be useful for building up registries manually.
77 * @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations
79 constructor(rulesConfig
) {
80 this.rules
= (rulesConfig
) ? makeRegistryItems(rulesConfig
) : {};
84 * Populate the registry with core rule configs.
86 * It will set the registry's `rule` property to an object having rule names
87 * as keys and an array of registryItems as values.
90 populateFromCoreRules() {
91 const rulesConfig
= configRule
.createCoreRuleConfigs(/* noDeprecated = */ true);
93 this.rules
= makeRegistryItems(rulesConfig
);
97 * Creates sets of rule configurations which can be used for linting
98 * and initializes registry errors to zero for those configurations (side effect).
100 * This combines as many rules together as possible, such that the first sets
101 * in the array will have the highest number of rules configured, and later sets
102 * will have fewer and fewer, as not all rules have the same number of possible
105 * The length of the returned array will be <= MAX_CONFIG_COMBINATIONS.
106 * @returns {Object[]} "rules" configurations to use for linting
110 const ruleIds
= Object
.keys(this.rules
),
114 * Add a rule configuration from the registry to the ruleSets
116 * This is broken out into its own function so that it doesn't need to be
117 * created inside of the while loop.
118 * @param {string} rule The ruleId to add.
121 const addRuleToRuleSet = function(rule
) {
124 * This check ensures that there is a rule configuration and that
125 * it has fewer than the max combinations allowed.
126 * If it has too many configs, we will only use the most basic of
127 * the possible configurations.
129 const hasFewCombos
= (this.rules
[rule
].length
<= MAX_CONFIG_COMBINATIONS
);
131 if (this.rules
[rule
][idx
] && (hasFewCombos
|| this.rules
[rule
][idx
].specificity
<= 2)) {
134 * If the rule has too many possible combinations, only take
135 * simple ones, avoiding objects.
137 if (!hasFewCombos
&& typeof this.rules
[rule
][idx
].config
[1] === "object") {
141 ruleSets
[idx
] = ruleSets
[idx
] || {};
142 ruleSets
[idx
][rule
] = this.rules
[rule
][idx
].config
;
145 * Initialize errorCount to zero, since this is a config which
148 this.rules
[rule
][idx
].errorCount
= 0;
152 while (ruleSets
.length
=== idx
) {
153 ruleIds
.forEach(addRuleToRuleSet
);
161 * Remove all items from the registry with a non-zero number of errors
163 * Note: this also removes rule configurations which were not linted
164 * (meaning, they have an undefined errorCount).
167 stripFailingConfigs() {
168 const ruleIds
= Object
.keys(this.rules
),
169 newRegistry
= new Registry();
171 newRegistry
.rules
= Object
.assign({}, this.rules
);
172 ruleIds
.forEach(ruleId
=> {
173 const errorFreeItems
= newRegistry
.rules
[ruleId
].filter(registryItem
=> (registryItem
.errorCount
=== 0));
175 if (errorFreeItems
.length
> 0) {
176 newRegistry
.rules
[ruleId
] = errorFreeItems
;
178 delete newRegistry
.rules
[ruleId
];
186 * Removes rule configurations which were not included in a ruleSet
189 stripExtraConfigs() {
190 const ruleIds
= Object
.keys(this.rules
),
191 newRegistry
= new Registry();
193 newRegistry
.rules
= Object
.assign({}, this.rules
);
194 ruleIds
.forEach(ruleId
=> {
195 newRegistry
.rules
[ruleId
] = newRegistry
.rules
[ruleId
].filter(registryItem
=> (typeof registryItem
.errorCount
!== "undefined"));
202 * Creates a registry of rules which had no error-free configs.
203 * The new registry is intended to be analyzed to determine whether its rules
204 * should be disabled or set to warning.
205 * @returns {Registry} A registry of failing rules.
207 getFailingRulesRegistry() {
208 const ruleIds
= Object
.keys(this.rules
),
209 failingRegistry
= new Registry();
211 ruleIds
.forEach(ruleId
=> {
212 const failingConfigs
= this.rules
[ruleId
].filter(registryItem
=> (registryItem
.errorCount
> 0));
214 if (failingConfigs
&& failingConfigs
.length
=== this.rules
[ruleId
].length
) {
215 failingRegistry
.rules
[ruleId
] = failingConfigs
;
219 return failingRegistry
;
223 * Create an eslint config for any rules which only have one configuration
225 * @returns {Object} An eslint config with rules section populated
228 const ruleIds
= Object
.keys(this.rules
),
229 config
= { rules
: {} };
231 ruleIds
.forEach(ruleId
=> {
232 if (this.rules
[ruleId
].length
=== 1) {
233 config
.rules
[ruleId
] = this.rules
[ruleId
][0].config
;
241 * Return a cloned registry containing only configs with a desired specificity
242 * @param {number} specificity Only keep configs with this specificity
243 * @returns {Registry} A registry of rules
245 filterBySpecificity(specificity
) {
246 const ruleIds
= Object
.keys(this.rules
),
247 newRegistry
= new Registry();
249 newRegistry
.rules
= Object
.assign({}, this.rules
);
250 ruleIds
.forEach(ruleId
=> {
251 newRegistry
.rules
[ruleId
] = this.rules
[ruleId
].filter(registryItem
=> (registryItem
.specificity
=== specificity
));
258 * Lint SourceCodes against all configurations in the registry, and record results
259 * @param {Object[]} sourceCodes SourceCode objects for each filename
260 * @param {Object} config ESLint config object
261 * @param {progressCallback} [cb] Optional callback for reporting execution status
262 * @returns {Registry} New registry with errorCount populated
264 lintSourceCode(sourceCodes
, config
, cb
) {
265 let lintedRegistry
= new Registry();
267 lintedRegistry
.rules
= Object
.assign({}, this.rules
);
269 const ruleSets
= lintedRegistry
.buildRuleSets();
271 lintedRegistry
= lintedRegistry
.stripExtraConfigs();
273 debug("Linting with all possible rule combinations");
275 const filenames
= Object
.keys(sourceCodes
);
276 const totalFilesLinting
= filenames
.length
* ruleSets
.length
;
278 filenames
.forEach(filename
=> {
279 debug(`Linting file: ${filename}`);
283 ruleSets
.forEach(ruleSet
=> {
284 const lintConfig
= Object
.assign({}, config
, { rules
: ruleSet
});
285 const lintResults
= linter
.verify(sourceCodes
[filename
], lintConfig
);
287 lintResults
.forEach(result
=> {
290 * It is possible that the error is from a configuration comment
291 * in a linted file, in which case there may not be a config
292 * set in this ruleSetIdx.
293 * (https://github.com/eslint/eslint/issues/5992)
294 * (https://github.com/eslint/eslint/issues/7860)
297 lintedRegistry
.rules
[result
.ruleId
] &&
298 lintedRegistry
.rules
[result
.ruleId
][ruleSetIdx
]
300 lintedRegistry
.rules
[result
.ruleId
][ruleSetIdx
].errorCount
+= 1;
307 cb(totalFilesLinting
); // eslint-disable-line node/callback-return -- End of function
312 sourceCodes
[filename
] = null;
315 return lintedRegistry
;
320 * Extract rule configuration into eslint:recommended where possible.
322 * This will return a new config with `["extends": [ ..., "eslint:recommended"]` and
323 * only the rules which have configurations different from the recommended config.
324 * @param {Object} config config object
325 * @returns {Object} config object using `"extends": ["eslint:recommended"]`
327 function extendFromRecommended(config
) {
328 const newConfig
= Object
.assign({}, config
);
330 ConfigOps
.normalizeToStrings(newConfig
);
332 const recRules
= Object
.keys(recConfig
.rules
).filter(ruleId
=> ConfigOps
.isErrorSeverity(recConfig
.rules
[ruleId
]));
334 recRules
.forEach(ruleId
=> {
335 if (equal(recConfig
.rules
[ruleId
], newConfig
.rules
[ruleId
])) {
336 delete newConfig
.rules
[ruleId
];
339 newConfig
.extends.unshift(RECOMMENDED_CONFIG_NAME
);
344 //------------------------------------------------------------------------------
346 //------------------------------------------------------------------------------
350 extendFromRecommended