| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 | /** * @fileoverview Used for creating a suggested configuration based on project code. * @author Ian VanSchooten */"use strict";//------------------------------------------------------------------------------// Requirements//------------------------------------------------------------------------------const lodash = require("lodash"),    Linter = require("../linter"),    configRule = require("./config-rule"),    ConfigOps = require("./config-ops"),    recConfig = require("../../conf/eslint-recommended");const debug = require("debug")("eslint:autoconfig");const linter = new Linter();//------------------------------------------------------------------------------// Data//------------------------------------------------------------------------------const MAX_CONFIG_COMBINATIONS = 17, // 16 combinations + 1 for severity only    RECOMMENDED_CONFIG_NAME = "eslint:recommended";//------------------------------------------------------------------------------// Private//------------------------------------------------------------------------------/** * Information about a rule configuration, in the context of a Registry. * * @typedef {Object}     registryItem * @param   {ruleConfig} config        A valid configuration for the rule * @param   {number}     specificity   The number of elements in the ruleConfig array * @param   {number}     errorCount    The number of errors encountered when linting with the config *//** * This callback is used to measure execution status in a progress bar * @callback progressCallback * @param {number} The total number of times the callback will be called. *//** * Create registryItems for rules * @param   {rulesConfig} rulesConfig Hash of rule names and arrays of ruleConfig items * @returns {Object}                  registryItems for each rule in provided rulesConfig */function makeRegistryItems(rulesConfig) {    return Object.keys(rulesConfig).reduce((accumulator, ruleId) => {        accumulator[ruleId] = rulesConfig[ruleId].map(config => ({            config,            specificity: config.length || 1,            errorCount: void 0        }));        return accumulator;    }, {});}/** * Creates an object in which to store rule configs and error counts * * Unless a rulesConfig is provided at construction, the registry will not contain * any rules, only methods.  This will be useful for building up registries manually. * * Registry class */class Registry {    /**     * @param {rulesConfig} [rulesConfig] Hash of rule names and arrays of possible configurations     */    constructor(rulesConfig) {        this.rules = (rulesConfig) ? makeRegistryItems(rulesConfig) : {};    }    /**     * Populate the registry with core rule configs.     *     * It will set the registry's `rule` property to an object having rule names     * as keys and an array of registryItems as values.     *     * @returns {void}     */    populateFromCoreRules() {        const rulesConfig = configRule.createCoreRuleConfigs();        this.rules = makeRegistryItems(rulesConfig);    }    /**     * Creates sets of rule configurations which can be used for linting     * and initializes registry errors to zero for those configurations (side effect).     *     * This combines as many rules together as possible, such that the first sets     * in the array will have the highest number of rules configured, and later sets     * will have fewer and fewer, as not all rules have the same number of possible     * configurations.     *     * The length of the returned array will be <= MAX_CONFIG_COMBINATIONS.     *     * @param   {Object}   registry The autoconfig registry     * @returns {Object[]}          "rules" configurations to use for linting     */    buildRuleSets() {        let idx = 0;        const ruleIds = Object.keys(this.rules),            ruleSets = [];        /**         * Add a rule configuration from the registry to the ruleSets         *         * This is broken out into its own function so that it doesn't need to be         * created inside of the while loop.         *         * @param   {string} rule The ruleId to add.         * @returns {void}         */        const addRuleToRuleSet = function(rule) {            /*             * This check ensures that there is a rule configuration and that             * it has fewer than the max combinations allowed.             * If it has too many configs, we will only use the most basic of             * the possible configurations.             */            const hasFewCombos = (this.rules[rule].length <= MAX_CONFIG_COMBINATIONS);            if (this.rules[rule][idx] && (hasFewCombos || this.rules[rule][idx].specificity <= 2)) {                /*                 * If the rule has too many possible combinations, only take                 * simple ones, avoiding objects.                 */                if (!hasFewCombos && typeof this.rules[rule][idx].config[1] === "object") {                    return;                }                ruleSets[idx] = ruleSets[idx] || {};                ruleSets[idx][rule] = this.rules[rule][idx].config;                /*                 * Initialize errorCount to zero, since this is a config which                 * will be linted.                 */                this.rules[rule][idx].errorCount = 0;            }        }.bind(this);        while (ruleSets.length === idx) {            ruleIds.forEach(addRuleToRuleSet);            idx += 1;        }        return ruleSets;    }    /**     * Remove all items from the registry with a non-zero number of errors     *     * Note: this also removes rule configurations which were not linted     * (meaning, they have an undefined errorCount).     *     * @returns {void}     */    stripFailingConfigs() {        const ruleIds = Object.keys(this.rules),            newRegistry = new Registry();        newRegistry.rules = Object.assign({}, this.rules);        ruleIds.forEach(ruleId => {            const errorFreeItems = newRegistry.rules[ruleId].filter(registryItem => (registryItem.errorCount === 0));            if (errorFreeItems.length > 0) {                newRegistry.rules[ruleId] = errorFreeItems;            } else {                delete newRegistry.rules[ruleId];            }        });        return newRegistry;    }    /**     * Removes rule configurations which were not included in a ruleSet     *     * @returns {void}     */    stripExtraConfigs() {        const ruleIds = Object.keys(this.rules),            newRegistry = new Registry();        newRegistry.rules = Object.assign({}, this.rules);        ruleIds.forEach(ruleId => {            newRegistry.rules[ruleId] = newRegistry.rules[ruleId].filter(registryItem => (typeof registryItem.errorCount !== "undefined"));        });        return newRegistry;    }    /**     * Creates a registry of rules which had no error-free configs.     * The new registry is intended to be analyzed to determine whether its rules     * should be disabled or set to warning.     *     * @returns {Registry}  A registry of failing rules.     */    getFailingRulesRegistry() {        const ruleIds = Object.keys(this.rules),            failingRegistry = new Registry();        ruleIds.forEach(ruleId => {            const failingConfigs = this.rules[ruleId].filter(registryItem => (registryItem.errorCount > 0));            if (failingConfigs && failingConfigs.length === this.rules[ruleId].length) {                failingRegistry.rules[ruleId] = failingConfigs;            }        });        return failingRegistry;    }    /**     * Create an eslint config for any rules which only have one configuration     * in the registry.     *     * @returns {Object} An eslint config with rules section populated     */    createConfig() {        const ruleIds = Object.keys(this.rules),            config = { rules: {} };        ruleIds.forEach(ruleId => {            if (this.rules[ruleId].length === 1) {                config.rules[ruleId] = this.rules[ruleId][0].config;            }        });        return config;    }    /**     * Return a cloned registry containing only configs with a desired specificity     *     * @param   {number} specificity Only keep configs with this specificity     * @returns {Registry}           A registry of rules     */    filterBySpecificity(specificity) {        const ruleIds = Object.keys(this.rules),            newRegistry = new Registry();        newRegistry.rules = Object.assign({}, this.rules);        ruleIds.forEach(ruleId => {            newRegistry.rules[ruleId] = this.rules[ruleId].filter(registryItem => (registryItem.specificity === specificity));        });        return newRegistry;    }    /**     * Lint SourceCodes against all configurations in the registry, and record results     *     * @param   {Object[]} sourceCodes  SourceCode objects for each filename     * @param   {Object}   config       ESLint config object     * @param   {progressCallback} [cb] Optional callback for reporting execution status     * @returns {Registry}              New registry with errorCount populated     */    lintSourceCode(sourceCodes, config, cb) {        let lintedRegistry = new Registry();        lintedRegistry.rules = Object.assign({}, this.rules);        const ruleSets = lintedRegistry.buildRuleSets();        lintedRegistry = lintedRegistry.stripExtraConfigs();        debug("Linting with all possible rule combinations");        const filenames = Object.keys(sourceCodes);        const totalFilesLinting = filenames.length * ruleSets.length;        filenames.forEach(filename => {            debug(`Linting file: ${filename}`);            let ruleSetIdx = 0;            ruleSets.forEach(ruleSet => {                const lintConfig = Object.assign({}, config, { rules: ruleSet });                const lintResults = linter.verify(sourceCodes[filename], lintConfig);                lintResults.forEach(result => {                    /*                     * It is possible that the error is from a configuration comment                     * in a linted file, in which case there may not be a config                     * set in this ruleSetIdx.                     * (https://github.com/eslint/eslint/issues/5992)                     * (https://github.com/eslint/eslint/issues/7860)                     */                    if (                        lintedRegistry.rules[result.ruleId] &&                        lintedRegistry.rules[result.ruleId][ruleSetIdx]                    ) {                        lintedRegistry.rules[result.ruleId][ruleSetIdx].errorCount += 1;                    }                });                ruleSetIdx += 1;                if (cb) {                    cb(totalFilesLinting); // eslint-disable-line callback-return                }            });            // Deallocate for GC            sourceCodes[filename] = null;        });        return lintedRegistry;    }}/** * Extract rule configuration into eslint:recommended where possible. * * This will return a new config with `"extends": "eslint:recommended"` and * only the rules which have configurations different from the recommended config. * * @param   {Object} config config object * @returns {Object}        config object using `"extends": "eslint:recommended"` */function extendFromRecommended(config) {    const newConfig = Object.assign({}, config);    ConfigOps.normalizeToStrings(newConfig);    const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId]));    recRules.forEach(ruleId => {        if (lodash.isEqual(recConfig.rules[ruleId], newConfig.rules[ruleId])) {            delete newConfig.rules[ruleId];        }    });    newConfig.extends = RECOMMENDED_CONFIG_NAME;    return newConfig;}//------------------------------------------------------------------------------// Public Interface//------------------------------------------------------------------------------module.exports = {    Registry,    extendFromRecommended};
 |