From 45c23f9e44af7b2fd3a3fd7fbe8b2e39dff82426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=AF=E7=84=B6?= Date: Wed, 8 Sep 2021 18:43:22 +0800 Subject: [PATCH] feat: move eslint --init to @eslint/create-config fixes #14768, fixes #15159 Update: rm eslint auto config refs: https://github.com/aladdin-add/rfcs/blob/bdc12aa062750d837e5a3bbbf2f6e5e3a98da388/designs/2021-init-command-eslint-cli/README.md#1-remove-eslint-auto-config Update: mv eslint --init to another package moved `lib/init/config-rule` to tools, as it is also used by some others refs: https://github.com/aladdin-add/rfcs/blob/bdc12aa062750d837e5a3bbbf2f6e5e3a98da388/designs/2021-init-command-eslint-cli/README.md#2-move-eslint---init-related-files-to-a-separate-repo chore: fix imports to make test passing todo: use the new eslint api fix: use the new eslint api (async) fix: remove espree from deps chore: fix a failing test fix: a failing test chore: cleanup TODOs fix: allow to use local-installed eslint wip: fix one-var chore: lib => esm chore: tests => esm todo: proxyquire => td chore: update deps to latest fix: should write a file through fs when a ${fileType} path is passed replaced proxyquire & sinon => td fix: should include a newline character at EOF chore: add testdouble --wip-- [skip ci] chore: remove package @eslint/create-eslint feat: update npm --init to run `npm init @eslint/config` docs: update getting-started Update README.md Update getting-started.md chore: rm init fixtures fix: `npm init @eslint/config` output chore: rm unused files chore: rm unused deps Update bin/eslint.js Co-authored-by: Brandon Mills chore: fix typo --- README.md | 4 +- bin/eslint.js | 8 +- docs/user-guide/getting-started.md | 8 +- lib/init/autoconfig.js | 351 --------- lib/init/config-file.js | 144 ---- lib/init/config-initializer.js | 709 ------------------ lib/init/npm-utils.js | 179 ----- lib/init/source-code-utils.js | 110 --- package.json | 5 +- .../config-initializer/lib/doubleQuotes.js | 1 - .../config-initializer/lib/no-semi.js | 1 - .../new-es-features/new-es-features.js | 3 - .../parse-error/parse-error.js | 1 - .../config-initializer/singleQuotes.js | 1 - .../config-initializer/tests/console-log.js | 1 - .../config-initializer/tests/doubleQuotes.js | 1 - tests/lib/init/autoconfig.js | 385 ---------- tests/lib/init/config-file.js | 159 ---- tests/lib/init/config-initializer.js | 577 -------------- tests/lib/init/config-rule.js | 329 -------- tests/lib/init/npm-utils.js | 228 ------ tests/lib/init/source-code-utils.js | 250 ------ tests/tools/eslint-fuzzer.js | 2 +- {lib/init => tools}/config-rule.js | 2 +- tools/eslint-fuzzer.js | 2 +- 25 files changed, 18 insertions(+), 3443 deletions(-) delete mode 100644 lib/init/autoconfig.js delete mode 100644 lib/init/config-file.js delete mode 100644 lib/init/config-initializer.js delete mode 100644 lib/init/npm-utils.js delete mode 100644 lib/init/source-code-utils.js delete mode 100644 tests/fixtures/config-initializer/lib/doubleQuotes.js delete mode 100644 tests/fixtures/config-initializer/lib/no-semi.js delete mode 100644 tests/fixtures/config-initializer/new-es-features/new-es-features.js delete mode 100644 tests/fixtures/config-initializer/parse-error/parse-error.js delete mode 100644 tests/fixtures/config-initializer/singleQuotes.js delete mode 100644 tests/fixtures/config-initializer/tests/console-log.js delete mode 100644 tests/fixtures/config-initializer/tests/doubleQuotes.js delete mode 100644 tests/lib/init/autoconfig.js delete mode 100644 tests/lib/init/config-file.js delete mode 100644 tests/lib/init/config-initializer.js delete mode 100644 tests/lib/init/config-rule.js delete mode 100644 tests/lib/init/npm-utils.js delete mode 100644 tests/lib/init/source-code-utils.js rename {lib/init => tools}/config-rule.js (99%) diff --git a/README.md b/README.md index e8b005d7de39..bd744d9b4512 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ $ npm install eslint --save-dev You should then set up a configuration file: ```sh -$ ./node_modules/.bin/eslint --init +$ npm init @eslint/config ``` After that, you can run ESLint on any file or directory like this: @@ -65,7 +65,7 @@ $ ./node_modules/.bin/eslint yourfile.js ## Configuration -After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: +After running `npm init @eslint/config`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this: ```json { diff --git a/bin/eslint.js b/bin/eslint.js index 6b05356b9dbf..d00a870c089c 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -124,7 +124,13 @@ ${message}`); // Call the config initializer if `--init` is present. if (process.argv.includes("--init")) { - await require("../lib/init/config-initializer").initializeConfig(); + + // `eslint --init` has been moved to `@eslint/create-config` + console.warn("You can also run this command directly using 'npm init @eslint/config'."); + + const spawn = require("cross-spawn"); + + spawn.sync("npm", ["init", "@eslint/config"], { encoding: "utf8", stdio: "inherit" }); return; } diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md index cf808ddc022c..e8353192b7e8 100644 --- a/docs/user-guide/getting-started.md +++ b/docs/user-guide/getting-started.md @@ -23,14 +23,14 @@ yarn add eslint --dev You should then set up a configuration file, and the easiest way to do that is to use the `--init` flag: ```sh -$ npx eslint --init +$ npm init @eslint/config # or -$ yarn run eslint --init +$ yarn create @eslint/config ``` -**Note:** `--init` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. +**Note:** `npm init @eslint/config` assumes you have a `package.json` file already. If you don't, make sure to run `npm init` or `yarn init` beforehand. After that, you can run ESLint on any file or directory like this: @@ -48,7 +48,7 @@ It is also possible to install ESLint globally rather than locally (using `npm i **Note:** If you are coming from a version before 1.0.0 please see the [migration guide](migrating-to-1.0.0.md). -After running `eslint --init`, you'll have a `.eslintrc.{js,yml,json}` file in your directory. In it, you'll see some rules configured like this: +After running `npm init @eslint/config`, you'll have a `.eslintrc.{js,yml,json}` file in your directory. In it, you'll see some rules configured like this: ```json { diff --git a/lib/init/autoconfig.js b/lib/init/autoconfig.js deleted file mode 100644 index ea2523421c2e..000000000000 --- a/lib/init/autoconfig.js +++ /dev/null @@ -1,351 +0,0 @@ -/** - * @fileoverview Used for creating a suggested configuration based on project code. - * @author Ian VanSchooten - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const equal = require("fast-deep-equal"), - recConfig = require("../../conf/eslint-recommended"), - { - Legacy: { - ConfigOps - } - } = require("@eslint/eslintrc"), - { Linter } = require("../linter"), - configRule = require("./config-rule"); - -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 - * @property {ruleConfig} config A valid configuration for the rule - * @property {number} specificity The number of elements in the ruleConfig array - * @property {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(/* noDeprecated = */ true); - - 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. - * @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 node/callback-return -- End of function - } - }); - - // 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 (equal(recConfig.rules[ruleId], newConfig.rules[ruleId])) { - delete newConfig.rules[ruleId]; - } - }); - newConfig.extends.unshift(RECOMMENDED_CONFIG_NAME); - return newConfig; -} - - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - Registry, - extendFromRecommended -}; diff --git a/lib/init/config-file.js b/lib/init/config-file.js deleted file mode 100644 index 9eb10fab3a40..000000000000 --- a/lib/init/config-file.js +++ /dev/null @@ -1,144 +0,0 @@ -/** - * @fileoverview Helper to locate and load configuration files. - * @author Nicholas C. Zakas - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const fs = require("fs"), - path = require("path"), - stringify = require("json-stable-stringify-without-jsonify"); - -const debug = require("debug")("eslint:config-file"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Determines sort order for object keys for json-stable-stringify - * - * see: https://github.com/samn/json-stable-stringify#cmp - * @param {Object} a The first comparison object ({key: akey, value: avalue}) - * @param {Object} b The second comparison object ({key: bkey, value: bvalue}) - * @returns {number} 1 or -1, used in stringify cmp method - */ -function sortByKey(a, b) { - return a.key > b.key ? 1 : -1; -} - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -/** - * Writes a configuration file in JSON format. - * @param {Object} config The configuration object to write. - * @param {string} filePath The filename to write to. - * @returns {void} - * @private - */ -function writeJSONConfigFile(config, filePath) { - debug(`Writing JSON config file: ${filePath}`); - - const content = `${stringify(config, { cmp: sortByKey, space: 4 })}\n`; - - fs.writeFileSync(filePath, content, "utf8"); -} - -/** - * Writes a configuration file in YAML format. - * @param {Object} config The configuration object to write. - * @param {string} filePath The filename to write to. - * @returns {void} - * @private - */ -function writeYAMLConfigFile(config, filePath) { - debug(`Writing YAML config file: ${filePath}`); - - // lazy load YAML to improve performance when not used - const yaml = require("js-yaml"); - - const content = yaml.dump(config, { sortKeys: true }); - - fs.writeFileSync(filePath, content, "utf8"); -} - -/** - * Writes a configuration file in JavaScript format. - * @param {Object} config The configuration object to write. - * @param {string} filePath The filename to write to. - * @throws {Error} If an error occurs linting the config file contents. - * @returns {void} - * @private - */ -function writeJSConfigFile(config, filePath) { - debug(`Writing JS config file: ${filePath}`); - - let contentToWrite; - const stringifiedContent = `module.exports = ${stringify(config, { cmp: sortByKey, space: 4 })};\n`; - - try { - const { CLIEngine } = require("../cli-engine"); - const linter = new CLIEngine({ - baseConfig: config, - fix: true, - useEslintrc: false - }); - const report = linter.executeOnText(stringifiedContent); - - contentToWrite = report.results[0].output || stringifiedContent; - } catch (e) { - debug("Error linting JavaScript config file, writing unlinted version"); - const errorMessage = e.message; - - contentToWrite = stringifiedContent; - e.message = "An error occurred while generating your JavaScript config file. "; - e.message += "A config file was still generated, but the config file itself may not follow your linting rules."; - e.message += `\nError: ${errorMessage}`; - throw e; - } finally { - fs.writeFileSync(filePath, contentToWrite, "utf8"); - } -} - -/** - * Writes a configuration file. - * @param {Object} config The configuration object to write. - * @param {string} filePath The filename to write to. - * @returns {void} - * @throws {Error} When an unknown file type is specified. - * @private - */ -function write(config, filePath) { - switch (path.extname(filePath)) { - case ".js": - case ".cjs": - writeJSConfigFile(config, filePath); - break; - - case ".json": - writeJSONConfigFile(config, filePath); - break; - - case ".yaml": - case ".yml": - writeYAMLConfigFile(config, filePath); - break; - - default: - throw new Error("Can't write to unknown file type."); - } -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - write -}; diff --git a/lib/init/config-initializer.js b/lib/init/config-initializer.js deleted file mode 100644 index 3c244b7bcc06..000000000000 --- a/lib/init/config-initializer.js +++ /dev/null @@ -1,709 +0,0 @@ -/** - * @fileoverview Config initialization wizard. - * @author Ilya Volodin - */ - - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const util = require("util"), - path = require("path"), - fs = require("fs"), - enquirer = require("enquirer"), - ProgressBar = require("progress"), - semver = require("semver"), - espree = require("espree"), - recConfig = require("../../conf/eslint-recommended"), - { - Legacy: { - ConfigOps, - naming - } - } = require("@eslint/eslintrc"), - log = require("../shared/logging"), - ModuleResolver = require("../shared/relative-module-resolver"), - autoconfig = require("./autoconfig.js"), - ConfigFile = require("./config-file"), - npmUtils = require("./npm-utils"), - { getSourceCodeOfFiles } = require("./source-code-utils"); - -const debug = require("debug")("eslint:config-initializer"); - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -/* istanbul ignore next: hard to test fs function */ -/** - * Create .eslintrc file in the current working directory - * @param {Object} config object that contains user's answers - * @param {string} format The file format to write to. - * @returns {void} - */ -function writeFile(config, format) { - - // default is .js - let extname = ".js"; - - if (format === "YAML") { - extname = ".yml"; - } else if (format === "JSON") { - extname = ".json"; - } else if (format === "JavaScript") { - const pkgJSONPath = npmUtils.findPackageJson(); - - if (pkgJSONPath) { - const pkgJSONContents = JSON.parse(fs.readFileSync(pkgJSONPath, "utf8")); - - if (pkgJSONContents.type === "module") { - extname = ".cjs"; - } - } - } - - const installedESLint = config.installedESLint; - - delete config.installedESLint; - - ConfigFile.write(config, `./.eslintrc${extname}`); - log.info(`Successfully created .eslintrc${extname} file in ${process.cwd()}`); - - if (installedESLint) { - log.info("ESLint was installed locally. We recommend using this local copy instead of your globally-installed copy."); - } -} - -/** - * Get the peer dependencies of the given module. - * This adds the gotten value to cache at the first time, then reuses it. - * In a process, this function is called twice, but `npmUtils.fetchPeerDependencies` needs to access network which is relatively slow. - * @param {string} moduleName The module name to get. - * @returns {Object} The peer dependencies of the given module. - * This object is the object of `peerDependencies` field of `package.json`. - * Returns null if npm was not found. - */ -function getPeerDependencies(moduleName) { - let result = getPeerDependencies.cache.get(moduleName); - - if (!result) { - log.info(`Checking peerDependencies of ${moduleName}`); - - result = npmUtils.fetchPeerDependencies(moduleName); - getPeerDependencies.cache.set(moduleName, result); - } - - return result; -} -getPeerDependencies.cache = new Map(); - -/** - * Return necessary plugins, configs, parsers, etc. based on the config - * @param {Object} config config object - * @param {boolean} [installESLint=true] If `false` is given, it does not install eslint. - * @returns {string[]} An array of modules to be installed. - */ -function getModulesList(config, installESLint) { - const modules = {}; - - // Create a list of modules which should be installed based on config - if (config.plugins) { - for (const plugin of config.plugins) { - const moduleName = naming.normalizePackageName(plugin, "eslint-plugin"); - - modules[moduleName] = "latest"; - } - } - if (config.extends) { - const extendList = Array.isArray(config.extends) ? config.extends : [config.extends]; - - for (const extend of extendList) { - if (extend.startsWith("eslint:") || extend.startsWith("plugin:")) { - continue; - } - const moduleName = naming.normalizePackageName(extend, "eslint-config"); - - modules[moduleName] = "latest"; - Object.assign( - modules, - getPeerDependencies(`${moduleName}@latest`) - ); - } - } - - const parser = config.parser || (config.parserOptions && config.parserOptions.parser); - - if (parser) { - modules[parser] = "latest"; - } - - if (installESLint === false) { - delete modules.eslint; - } else { - const installStatus = npmUtils.checkDevDeps(["eslint"]); - - // Mark to show messages if it's new installation of eslint. - if (installStatus.eslint === false) { - log.info("Local ESLint installation not found."); - modules.eslint = modules.eslint || "latest"; - config.installedESLint = true; - } - } - - return Object.keys(modules).map(name => `${name}@${modules[name]}`); -} - -/** - * Set the `rules` of a config by examining a user's source code - * - * Note: This clones the config object and returns a new config to avoid mutating - * the original config parameter. - * @param {Object} answers answers received from enquirer - * @param {Object} config config object - * @throws {Error} If source code retrieval fails or source code file count is 0. - * @returns {Object} config object with configured rules - */ -function configureRules(answers, config) { - const BAR_TOTAL = 20, - BAR_SOURCE_CODE_TOTAL = 4, - newConfig = Object.assign({}, config), - disabledConfigs = {}; - let sourceCodes, - registry; - - // Set up a progress bar, as this process can take a long time - const bar = new ProgressBar("Determining Config: :percent [:bar] :elapseds elapsed, eta :etas ", { - width: 30, - total: BAR_TOTAL - }); - - bar.tick(0); // Shows the progress bar - - // Get the SourceCode of all chosen files - const patterns = answers.patterns.split(/[\s]+/u); - - try { - sourceCodes = getSourceCodeOfFiles(patterns, { baseConfig: newConfig, useEslintrc: false }, total => { - bar.tick((BAR_SOURCE_CODE_TOTAL / total)); - }); - } catch (e) { - log.info("\n"); - throw e; - } - const fileQty = Object.keys(sourceCodes).length; - - if (fileQty === 0) { - log.info("\n"); - throw new Error("Automatic Configuration failed. No files were able to be parsed."); - } - - // Create a registry of rule configs - registry = new autoconfig.Registry(); - registry.populateFromCoreRules(); - - // Lint all files with each rule config in the registry - registry = registry.lintSourceCode(sourceCodes, newConfig, total => { - bar.tick((BAR_TOTAL - BAR_SOURCE_CODE_TOTAL) / total); // Subtract out ticks used at beginning - }); - debug(`\nRegistry: ${util.inspect(registry.rules, { depth: null })}`); - - // Create a list of recommended rules, because we don't want to disable them - const recRules = Object.keys(recConfig.rules).filter(ruleId => ConfigOps.isErrorSeverity(recConfig.rules[ruleId])); - - // Find and disable rules which had no error-free configuration - const failingRegistry = registry.getFailingRulesRegistry(); - - Object.keys(failingRegistry.rules).forEach(ruleId => { - - // If the rule is recommended, set it to error, otherwise disable it - disabledConfigs[ruleId] = (recRules.indexOf(ruleId) !== -1) ? 2 : 0; - }); - - // Now that we know which rules to disable, strip out configs with errors - registry = registry.stripFailingConfigs(); - - /* - * If there is only one config that results in no errors for a rule, we should use it. - * createConfig will only add rules that have one configuration in the registry. - */ - const singleConfigs = registry.createConfig().rules; - - /* - * The "sweet spot" for number of options in a config seems to be two (severity plus one option). - * Very often, a third option (usually an object) is available to address - * edge cases, exceptions, or unique situations. We will prefer to use a config with - * specificity of two. - */ - const specTwoConfigs = registry.filterBySpecificity(2).createConfig().rules; - - // Maybe a specific combination using all three options works - const specThreeConfigs = registry.filterBySpecificity(3).createConfig().rules; - - // If all else fails, try to use the default (severity only) - const defaultConfigs = registry.filterBySpecificity(1).createConfig().rules; - - // Combine configs in reverse priority order (later take precedence) - newConfig.rules = Object.assign({}, disabledConfigs, defaultConfigs, specThreeConfigs, specTwoConfigs, singleConfigs); - - // Make sure progress bar has finished (floating point rounding) - bar.update(BAR_TOTAL); - - // Log out some stats to let the user know what happened - const finalRuleIds = Object.keys(newConfig.rules); - const totalRules = finalRuleIds.length; - const enabledRules = finalRuleIds.filter(ruleId => (newConfig.rules[ruleId] !== 0)).length; - const resultMessage = [ - `\nEnabled ${enabledRules} out of ${totalRules}`, - `rules based on ${fileQty}`, - `file${(fileQty === 1) ? "." : "s."}` - ].join(" "); - - log.info(resultMessage); - - ConfigOps.normalizeToStrings(newConfig); - return newConfig; -} - -/** - * process user's answers and create config object - * @param {Object} answers answers received from enquirer - * @returns {Object} config object - */ -function processAnswers(answers) { - let config = { - rules: {}, - env: {}, - parserOptions: {}, - extends: [] - }; - - config.parserOptions.ecmaVersion = espree.latestEcmaVersion; - config.env.es2021 = true; - - // set the module type - if (answers.moduleType === "esm") { - config.parserOptions.sourceType = "module"; - } else if (answers.moduleType === "commonjs") { - config.env.commonjs = true; - } - - // add in browser and node environments if necessary - answers.env.forEach(env => { - config.env[env] = true; - }); - - // add in library information - if (answers.framework === "react") { - config.parserOptions.ecmaFeatures = { - jsx: true - }; - config.plugins = ["react"]; - config.extends.push("plugin:react/recommended"); - } else if (answers.framework === "vue") { - config.plugins = ["vue"]; - config.extends.push("plugin:vue/essential"); - } - - if (answers.typescript) { - if (answers.framework === "vue") { - config.parserOptions.parser = "@typescript-eslint/parser"; - } else { - config.parser = "@typescript-eslint/parser"; - } - - if (Array.isArray(config.plugins)) { - config.plugins.push("@typescript-eslint"); - } else { - config.plugins = ["@typescript-eslint"]; - } - } - - // setup rules based on problems/style enforcement preferences - if (answers.purpose === "problems") { - config.extends.unshift("eslint:recommended"); - } else if (answers.purpose === "style") { - if (answers.source === "prompt") { - config.extends.unshift("eslint:recommended"); - config.rules.indent = ["error", answers.indent]; - config.rules.quotes = ["error", answers.quotes]; - config.rules["linebreak-style"] = ["error", answers.linebreak]; - config.rules.semi = ["error", answers.semi ? "always" : "never"]; - } else if (answers.source === "auto") { - config = configureRules(answers, config); - config = autoconfig.extendFromRecommended(config); - } - } - if (answers.typescript && config.extends.includes("eslint:recommended")) { - config.extends.push("plugin:@typescript-eslint/recommended"); - } - - // normalize extends - if (config.extends.length === 0) { - delete config.extends; - } else if (config.extends.length === 1) { - config.extends = config.extends[0]; - } - - ConfigOps.normalizeToStrings(config); - return config; -} - -/** - * Get the version of the local ESLint. - * @returns {string|null} The version. If the local ESLint was not found, returns null. - */ -function getLocalESLintVersion() { - try { - const eslintPath = ModuleResolver.resolve("eslint", path.join(process.cwd(), "__placeholder__.js")); - const eslint = require(eslintPath); - - return eslint.linter.version || null; - } catch { - return null; - } -} - -/** - * Get the shareable config name of the chosen style guide. - * @param {Object} answers The answers object. - * @returns {string} The shareable config name. - */ -function getStyleGuideName(answers) { - if (answers.styleguide === "airbnb" && answers.framework !== "react") { - return "airbnb-base"; - } - return answers.styleguide; -} - -/** - * Check whether the local ESLint version conflicts with the required version of the chosen shareable config. - * @param {Object} answers The answers object. - * @returns {boolean} `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config. - */ -function hasESLintVersionConflict(answers) { - - // Get the local ESLint version. - const localESLintVersion = getLocalESLintVersion(); - - if (!localESLintVersion) { - return false; - } - - // Get the required range of ESLint version. - const configName = getStyleGuideName(answers); - const moduleName = `eslint-config-${configName}@latest`; - const peerDependencies = getPeerDependencies(moduleName) || {}; - const requiredESLintVersionRange = peerDependencies.eslint; - - if (!requiredESLintVersionRange) { - return false; - } - - answers.localESLintVersion = localESLintVersion; - answers.requiredESLintVersionRange = requiredESLintVersionRange; - - // Check the version. - if (semver.satisfies(localESLintVersion, requiredESLintVersionRange)) { - answers.installESLint = false; - return false; - } - - return true; -} - -/** - * Install modules. - * @param {string[]} modules Modules to be installed. - * @returns {void} - */ -function installModules(modules) { - log.info(`Installing ${modules.join(", ")}`); - npmUtils.installSyncSaveDev(modules); -} - -/* istanbul ignore next: no need to test enquirer */ -/** - * Ask user to install modules. - * @param {string[]} modules Array of modules to be installed. - * @param {boolean} packageJsonExists Indicates if package.json is existed. - * @returns {Promise} Answer that indicates if user wants to install. - */ -function askInstallModules(modules, packageJsonExists) { - - // If no modules, do nothing. - if (modules.length === 0) { - return Promise.resolve(); - } - - log.info("The config that you've selected requires the following dependencies:\n"); - log.info(modules.join(" ")); - return enquirer.prompt([ - { - type: "toggle", - name: "executeInstallation", - message: "Would you like to install them now with npm?", - enabled: "Yes", - disabled: "No", - initial: 1, - skip() { - return !(modules.length && packageJsonExists); - }, - result(input) { - return this.skipped ? null : input; - } - } - ]).then(({ executeInstallation }) => { - if (executeInstallation) { - installModules(modules); - } - }); -} - -/* istanbul ignore next: no need to test enquirer */ -/** - * Ask use a few questions on command prompt - * @returns {Promise} The promise with the result of the prompt - */ -function promptUser() { - - return enquirer.prompt([ - { - type: "select", - name: "purpose", - message: "How would you like to use ESLint?", - - // The returned number matches the name value of nth in the choices array. - initial: 1, - choices: [ - { message: "To check syntax only", name: "syntax" }, - { message: "To check syntax and find problems", name: "problems" }, - { message: "To check syntax, find problems, and enforce code style", name: "style" } - ] - }, - { - type: "select", - name: "moduleType", - message: "What type of modules does your project use?", - initial: 0, - choices: [ - { message: "JavaScript modules (import/export)", name: "esm" }, - { message: "CommonJS (require/exports)", name: "commonjs" }, - { message: "None of these", name: "none" } - ] - }, - { - type: "select", - name: "framework", - message: "Which framework does your project use?", - initial: 0, - choices: [ - { message: "React", name: "react" }, - { message: "Vue.js", name: "vue" }, - { message: "None of these", name: "none" } - ] - }, - { - type: "toggle", - name: "typescript", - message: "Does your project use TypeScript?", - enabled: "Yes", - disabled: "No", - initial: 0 - }, - { - type: "multiselect", - name: "env", - message: "Where does your code run?", - hint: "(Press to select, to toggle all, to invert selection)", - initial: 0, - choices: [ - { message: "Browser", name: "browser" }, - { message: "Node", name: "node" } - ] - }, - { - type: "select", - name: "source", - message: "How would you like to define a style for your project?", - choices: [ - { message: "Use a popular style guide", name: "guide" }, - { message: "Answer questions about your style", name: "prompt" }, - { message: "Inspect your JavaScript file(s)", name: "auto" } - ], - skip() { - return this.state.answers.purpose !== "style"; - }, - result(input) { - return this.skipped ? null : input; - } - }, - { - type: "select", - name: "styleguide", - message: "Which style guide do you want to follow?", - choices: [ - { message: "Airbnb: https://github.com/airbnb/javascript", name: "airbnb" }, - { message: "Standard: https://github.com/standard/standard", name: "standard" }, - { message: "Google: https://github.com/google/eslint-config-google", name: "google" }, - { message: "XO: https://github.com/xojs/eslint-config-xo", name: "xo" } - ], - skip() { - this.state.answers.packageJsonExists = npmUtils.checkPackageJson(); - return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists); - }, - result(input) { - return this.skipped ? null : input; - } - }, - { - type: "input", - name: "patterns", - message: "Which file(s), path(s), or glob(s) should be examined?", - skip() { - return this.state.answers.source !== "auto"; - }, - validate(input) { - if (!this.skipped && input.trim().length === 0 && input.trim() !== ",") { - return "You must tell us what code to examine. Try again."; - } - return true; - } - }, - { - type: "select", - name: "format", - message: "What format do you want your config file to be in?", - initial: 0, - choices: ["JavaScript", "YAML", "JSON"] - }, - { - type: "toggle", - name: "installESLint", - message() { - const { answers } = this.state; - const verb = semver.ltr(answers.localESLintVersion, answers.requiredESLintVersionRange) - ? "upgrade" - : "downgrade"; - - return `The style guide "${answers.styleguide}" requires eslint@${answers.requiredESLintVersionRange}. You are currently using eslint@${answers.localESLintVersion}.\n Do you want to ${verb}?`; - }, - enabled: "Yes", - disabled: "No", - initial: 1, - skip() { - return !(this.state.answers.source === "guide" && this.state.answers.packageJsonExists && hasESLintVersionConflict(this.state.answers)); - }, - result(input) { - return this.skipped ? null : input; - } - } - ]).then(earlyAnswers => { - - // early exit if no style guide is necessary - if (earlyAnswers.purpose !== "style") { - const config = processAnswers(earlyAnswers); - const modules = getModulesList(config); - - return askInstallModules(modules, earlyAnswers.packageJsonExists) - .then(() => writeFile(config, earlyAnswers.format)); - } - - // early exit if you are using a style guide - if (earlyAnswers.source === "guide") { - if (!earlyAnswers.packageJsonExists) { - log.info("A package.json is necessary to install plugins such as style guides. Run `npm init` to create a package.json file and try again."); - return void 0; - } - if (earlyAnswers.installESLint === false && !semver.satisfies(earlyAnswers.localESLintVersion, earlyAnswers.requiredESLintVersionRange)) { - log.info(`Note: it might not work since ESLint's version is mismatched with the ${earlyAnswers.styleguide} config.`); - } - if (earlyAnswers.styleguide === "airbnb" && earlyAnswers.framework !== "react") { - earlyAnswers.styleguide = "airbnb-base"; - } - - const config = processAnswers(earlyAnswers); - - if (Array.isArray(config.extends)) { - config.extends.push(earlyAnswers.styleguide); - } else if (config.extends) { - config.extends = [config.extends, earlyAnswers.styleguide]; - } else { - config.extends = [earlyAnswers.styleguide]; - } - - const modules = getModulesList(config); - - return askInstallModules(modules, earlyAnswers.packageJsonExists) - .then(() => writeFile(config, earlyAnswers.format)); - - } - - if (earlyAnswers.source === "auto") { - const combinedAnswers = Object.assign({}, earlyAnswers); - const config = processAnswers(combinedAnswers); - const modules = getModulesList(config); - - return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format)); - } - - // continue with the style questions otherwise... - return enquirer.prompt([ - { - type: "select", - name: "indent", - message: "What style of indentation do you use?", - initial: 0, - choices: [{ message: "Tabs", name: "tab" }, { message: "Spaces", name: 4 }] - }, - { - type: "select", - name: "quotes", - message: "What quotes do you use for strings?", - initial: 0, - choices: [{ message: "Double", name: "double" }, { message: "Single", name: "single" }] - }, - { - type: "select", - name: "linebreak", - message: "What line endings do you use?", - initial: 0, - choices: [{ message: "Unix", name: "unix" }, { message: "Windows", name: "windows" }] - }, - { - type: "toggle", - name: "semi", - message: "Do you require semicolons?", - enabled: "Yes", - disabled: "No", - initial: 1 - } - ]).then(answers => { - const totalAnswers = Object.assign({}, earlyAnswers, answers); - - const config = processAnswers(totalAnswers); - const modules = getModulesList(config); - - return askInstallModules(modules).then(() => writeFile(config, earlyAnswers.format)); - }); - }); -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -const init = { - getModulesList, - hasESLintVersionConflict, - installModules, - processAnswers, - writeFile, - /* istanbul ignore next */initializeConfig() { - return promptUser(); - } -}; - -module.exports = init; diff --git a/lib/init/npm-utils.js b/lib/init/npm-utils.js deleted file mode 100644 index 4a8efe964f33..000000000000 --- a/lib/init/npm-utils.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * @fileoverview Utility for executing npm commands. - * @author Ian VanSchooten - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const fs = require("fs"), - spawn = require("cross-spawn"), - path = require("path"), - log = require("../shared/logging"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Find the closest package.json file, starting at process.cwd (by default), - * and working up to root. - * @param {string} [startDir=process.cwd()] Starting directory - * @returns {string} Absolute path to closest package.json file - */ -function findPackageJson(startDir) { - let dir = path.resolve(startDir || process.cwd()); - - do { - const pkgFile = path.join(dir, "package.json"); - - if (!fs.existsSync(pkgFile) || !fs.statSync(pkgFile).isFile()) { - dir = path.join(dir, ".."); - continue; - } - return pkgFile; - } while (dir !== path.resolve(dir, "..")); - return null; -} - -//------------------------------------------------------------------------------ -// Private -//------------------------------------------------------------------------------ - -/** - * Install node modules synchronously and save to devDependencies in package.json - * @param {string|string[]} packages Node module or modules to install - * @returns {void} - */ -function installSyncSaveDev(packages) { - const packageList = Array.isArray(packages) ? packages : [packages]; - const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packageList), { stdio: "inherit" }); - const error = npmProcess.error; - - if (error && error.code === "ENOENT") { - const pluralS = packageList.length > 1 ? "s" : ""; - - log.error(`Could not execute npm. Please install the following package${pluralS} with a package manager of your choice: ${packageList.join(", ")}`); - } -} - -/** - * Fetch `peerDependencies` of the given package by `npm show` command. - * @param {string} packageName The package name to fetch peerDependencies. - * @returns {Object} Gotten peerDependencies. Returns null if npm was not found. - */ -function fetchPeerDependencies(packageName) { - const npmProcess = spawn.sync( - "npm", - ["show", "--json", packageName, "peerDependencies"], - { encoding: "utf8" } - ); - - const error = npmProcess.error; - - if (error && error.code === "ENOENT") { - return null; - } - const fetchedText = npmProcess.stdout.trim(); - - return JSON.parse(fetchedText || "{}"); - - -} - -/** - * Check whether node modules are include in a project's package.json. - * @param {string[]} packages Array of node module names - * @param {Object} opt Options Object - * @param {boolean} opt.dependencies Set to true to check for direct dependencies - * @param {boolean} opt.devDependencies Set to true to check for development dependencies - * @param {boolean} opt.startdir Directory to begin searching from - * @throws {Error} If cannot find valid `package.json` file. - * @returns {Object} An object whose keys are the module names - * and values are booleans indicating installation. - */ -function check(packages, opt) { - const deps = new Set(); - const pkgJson = (opt) ? findPackageJson(opt.startDir) : findPackageJson(); - let fileJson; - - if (!pkgJson) { - throw new Error("Could not find a package.json file. Run 'npm init' to create one."); - } - - try { - fileJson = JSON.parse(fs.readFileSync(pkgJson, "utf8")); - } catch (e) { - const error = new Error(e); - - error.messageTemplate = "failed-to-read-json"; - error.messageData = { - path: pkgJson, - message: e.message - }; - throw error; - } - - ["dependencies", "devDependencies"].forEach(key => { - if (opt[key] && typeof fileJson[key] === "object") { - Object.keys(fileJson[key]).forEach(dep => deps.add(dep)); - } - }); - - return packages.reduce((status, pkg) => { - status[pkg] = deps.has(pkg); - return status; - }, {}); -} - -/** - * Check whether node modules are included in the dependencies of a project's - * package.json. - * - * Convenience wrapper around check(). - * @param {string[]} packages Array of node modules to check. - * @param {string} rootDir The directory containing a package.json - * @returns {Object} An object whose keys are the module names - * and values are booleans indicating installation. - */ -function checkDeps(packages, rootDir) { - return check(packages, { dependencies: true, startDir: rootDir }); -} - -/** - * Check whether node modules are included in the devDependencies of a project's - * package.json. - * - * Convenience wrapper around check(). - * @param {string[]} packages Array of node modules to check. - * @returns {Object} An object whose keys are the module names - * and values are booleans indicating installation. - */ -function checkDevDeps(packages) { - return check(packages, { devDependencies: true }); -} - -/** - * Check whether package.json is found in current path. - * @param {string} [startDir] Starting directory - * @returns {boolean} Whether a package.json is found in current path. - */ -function checkPackageJson(startDir) { - return !!findPackageJson(startDir); -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - -module.exports = { - installSyncSaveDev, - fetchPeerDependencies, - findPackageJson, - checkDeps, - checkDevDeps, - checkPackageJson -}; diff --git a/lib/init/source-code-utils.js b/lib/init/source-code-utils.js deleted file mode 100644 index 08c20e5d56e2..000000000000 --- a/lib/init/source-code-utils.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @fileoverview Tools for obtaining SourceCode objects. - * @author Ian VanSchooten - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const { CLIEngine } = require("../cli-engine"); - -/* - * This is used for: - * - * 1. Enumerate target file because we have not expose such a API on `CLIEngine` - * (https://github.com/eslint/eslint/issues/11222). - * 2. Create `SourceCode` instances. Because we don't have any function which - * instantiate `SourceCode` so it needs to take the created `SourceCode` - * instance out after linting. - * - * TODO1: Expose the API that enumerates target files. - * TODO2: Extract the creation logic of `SourceCode` from `Linter` class. - */ -const { getCLIEngineInternalSlots } = require("../cli-engine/cli-engine"); // eslint-disable-line node/no-restricted-require -- Todo - -const debug = require("debug")("eslint:source-code-utils"); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Get the SourceCode object for a single file - * @param {string} filename The fully resolved filename to get SourceCode from. - * @param {Object} engine A CLIEngine. - * @throws {Error} Upon fatal errors from execution. - * @returns {Array} Array of the SourceCode object representing the file - * and fatal error message. - */ -function getSourceCodeOfFile(filename, engine) { - debug("getting sourceCode of", filename); - const results = engine.executeOnFiles([filename]); - - if (results && results.results[0] && results.results[0].messages[0] && results.results[0].messages[0].fatal) { - const msg = results.results[0].messages[0]; - - throw new Error(`(${filename}:${msg.line}:${msg.column}) ${msg.message}`); - } - - // TODO: extract the logic that creates source code objects to `SourceCode#parse(text, options)` or something like. - const { linter } = getCLIEngineInternalSlots(engine); - const sourceCode = linter.getSourceCode(); - - return sourceCode; -} - -//------------------------------------------------------------------------------ -// Public Interface -//------------------------------------------------------------------------------ - - -/** - * 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. - */ - -/** - * Gets the SourceCode of a single file, or set of files. - * @param {string[]|string} patterns A filename, directory name, or glob, or an array of them - * @param {Object} options A CLIEngine options object. If not provided, the default cli options will be used. - * @param {progressCallback} callback Callback for reporting execution status - * @returns {Object} The SourceCode of all processed files. - */ -function getSourceCodeOfFiles(patterns, options, callback) { - const sourceCodes = {}; - const globPatternsList = typeof patterns === "string" ? [patterns] : patterns; - const engine = new CLIEngine({ ...options, rules: {} }); - - // TODO: make file iteration as a public API and use it. - const { fileEnumerator } = getCLIEngineInternalSlots(engine); - const filenames = - Array.from(fileEnumerator.iterateFiles(globPatternsList)) - .filter(entry => !entry.ignored) - .map(entry => entry.filePath); - - if (filenames.length === 0) { - debug(`Did not find any files matching pattern(s): ${globPatternsList}`); - } - - filenames.forEach(filename => { - const sourceCode = getSourceCodeOfFile(filename, engine); - - if (sourceCode) { - debug("got sourceCode of", filename); - sourceCodes[filename] = sourceCode; - } - if (callback) { - callback(filenames.length); // eslint-disable-line node/callback-return -- End of function - } - }); - - return sourceCodes; -} - -module.exports = { - getSourceCodeOfFiles -}; diff --git a/package.json b/package.json index fee3bf0f67b2..dfb28d5eaac6 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", - "enquirer": "^2.3.5", "escape-string-regexp": "^4.0.0", "eslint-scope": "^7.1.0", "eslint-utils": "^3.0.0", @@ -78,9 +77,7 @@ "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", - "progress": "^2.0.0", "regexpp": "^3.2.0", - "semver": "^7.2.1", "strip-ansi": "^6.0.1", "strip-json-comments": "^3.1.0", "text-table": "^0.2.0", @@ -124,10 +121,12 @@ "node-polyfill-webpack-plugin": "^1.0.3", "npm-license": "^0.3.3", "nyc": "^15.0.1", + "progress": "^2.0.3", "proxyquire": "^2.0.1", "puppeteer": "^9.1.1", "recast": "^0.20.4", "regenerator-runtime": "^0.13.2", + "semver": "^7.3.5", "shelljs": "^0.8.2", "sinon": "^11.0.0", "temp": "^0.9.0", diff --git a/tests/fixtures/config-initializer/lib/doubleQuotes.js b/tests/fixtures/config-initializer/lib/doubleQuotes.js deleted file mode 100644 index 1cd4a2abbe20..000000000000 --- a/tests/fixtures/config-initializer/lib/doubleQuotes.js +++ /dev/null @@ -1 +0,0 @@ -var foo = "doubleQuotes"; diff --git a/tests/fixtures/config-initializer/lib/no-semi.js b/tests/fixtures/config-initializer/lib/no-semi.js deleted file mode 100644 index bf10b1cfd192..000000000000 --- a/tests/fixtures/config-initializer/lib/no-semi.js +++ /dev/null @@ -1 +0,0 @@ -var name = "ESLint" diff --git a/tests/fixtures/config-initializer/new-es-features/new-es-features.js b/tests/fixtures/config-initializer/new-es-features/new-es-features.js deleted file mode 100644 index c3e03fc32ca3..000000000000 --- a/tests/fixtures/config-initializer/new-es-features/new-es-features.js +++ /dev/null @@ -1,3 +0,0 @@ -async function fn() { - await Promise.resolve(); -} diff --git a/tests/fixtures/config-initializer/parse-error/parse-error.js b/tests/fixtures/config-initializer/parse-error/parse-error.js deleted file mode 100644 index 90bd920ceff5..000000000000 --- a/tests/fixtures/config-initializer/parse-error/parse-error.js +++ /dev/null @@ -1 +0,0 @@ -+; diff --git a/tests/fixtures/config-initializer/singleQuotes.js b/tests/fixtures/config-initializer/singleQuotes.js deleted file mode 100644 index db1e01feda6b..000000000000 --- a/tests/fixtures/config-initializer/singleQuotes.js +++ /dev/null @@ -1 +0,0 @@ -var foo = 'singleQuotes'; diff --git a/tests/fixtures/config-initializer/tests/console-log.js b/tests/fixtures/config-initializer/tests/console-log.js deleted file mode 100644 index 929e82a146d2..000000000000 --- a/tests/fixtures/config-initializer/tests/console-log.js +++ /dev/null @@ -1 +0,0 @@ -console.log("I'm a log"); diff --git a/tests/fixtures/config-initializer/tests/doubleQuotes.js b/tests/fixtures/config-initializer/tests/doubleQuotes.js deleted file mode 100644 index 1cd4a2abbe20..000000000000 --- a/tests/fixtures/config-initializer/tests/doubleQuotes.js +++ /dev/null @@ -1 +0,0 @@ -var foo = "doubleQuotes"; diff --git a/tests/lib/init/autoconfig.js b/tests/lib/init/autoconfig.js deleted file mode 100644 index 3db6a63645f9..000000000000 --- a/tests/lib/init/autoconfig.js +++ /dev/null @@ -1,385 +0,0 @@ -/** - * @fileoverview Used for creating a suggested configuration based on project code. - * @author Ian VanSchooten - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - autoconfig = require("../../../lib/init/autoconfig"), - sourceCodeUtils = require("../../../lib/init/source-code-utils"), - baseDefaultOptions = require("../../../conf/default-cli-options"), - recommendedConfig = require("../../conf/eslint-recommended"); - -const defaultOptions = Object.assign({}, baseDefaultOptions, { cwd: process.cwd() }); - -//------------------------------------------------------------------------------ -// Data -//------------------------------------------------------------------------------ - -const SOURCE_CODE_FIXTURE_FILENAME = "./tests/fixtures/autoconfig/source.js"; -const CONFIG_COMMENTS_FILENAME = "./tests/fixtures/autoconfig/source-with-comments.js"; -const SEVERITY = 2; - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const rulesConfig = { - semi: [SEVERITY, [SEVERITY, "always"], [SEVERITY, "never"]], - "semi-spacing": [SEVERITY, - [SEVERITY, { before: true, after: true }], - [SEVERITY, { before: true, after: false }], - [SEVERITY, { before: false, after: true }], - [SEVERITY, { before: false, after: false }] - ], - quotes: [SEVERITY, - [SEVERITY, "single"], - [SEVERITY, "double"], - [SEVERITY, "backtick"], - [SEVERITY, "single", "avoid-escape"], - [SEVERITY, "double", "avoid-escape"], - [SEVERITY, "backtick", "avoid-escape"]] -}; - -const errorRulesConfig = { - "no-unused-vars": [SEVERITY], - "semi-spacing": [SEVERITY, - [SEVERITY, { before: true, after: true }], - [SEVERITY, { before: true, after: false }], - [SEVERITY, { before: false, after: true }], - [SEVERITY, { before: false, after: false }] - ] -}; - -describe("autoconfig", () => { - - describe("Registry", () => { - - it("should set up a registry for rules in a provided rulesConfig", () => { - const expectedRules = Object.keys(rulesConfig); - const registry = new autoconfig.Registry(rulesConfig); - - assert.strictEqual(Object.keys(registry.rules).length, 3); - assert.sameMembers(Object.keys(registry.rules), expectedRules); - assert.isArray(registry.rules.semi); - assert.isArray(registry.rules["semi-spacing"]); - assert.isArray(registry.rules.quotes); - assert.lengthOf(registry.rules.semi, 3); - assert.lengthOf(registry.rules["semi-spacing"], 5); - assert.lengthOf(registry.rules.quotes, 7); - }); - - it("should not have any rules if constructed without a config argument", () => { - const registry = new autoconfig.Registry(); - - assert.isObject(registry.rules); - assert.lengthOf(Object.keys(registry.rules), 0); - }); - - it("should create registryItems for each rule with the proper keys", () => { - const registry = new autoconfig.Registry(rulesConfig); - - assert.isObject(registry.rules.semi[0]); - assert.isObject(registry.rules["semi-spacing"][0]); - assert.isObject(registry.rules.quotes[0]); - assert.property(registry.rules.semi[0], "config"); - assert.property(registry.rules.semi[0], "specificity"); - assert.property(registry.rules.semi[0], "errorCount"); - }); - - it("should populate the config property correctly", () => { - const registry = new autoconfig.Registry(rulesConfig); - - assert.strictEqual(registry.rules.quotes[0].config, SEVERITY); - assert.deepStrictEqual(registry.rules.quotes[1].config, [SEVERITY, "single"]); - assert.deepStrictEqual(registry.rules.quotes[2].config, [SEVERITY, "double"]); - assert.deepStrictEqual(registry.rules.quotes[3].config, [SEVERITY, "backtick"]); - assert.deepStrictEqual(registry.rules.quotes[4].config, [SEVERITY, "single", "avoid-escape"]); - assert.deepStrictEqual(registry.rules.quotes[5].config, [SEVERITY, "double", "avoid-escape"]); - assert.deepStrictEqual(registry.rules.quotes[6].config, [SEVERITY, "backtick", "avoid-escape"]); - }); - - it("should assign the correct specificity", () => { - const registry = new autoconfig.Registry(rulesConfig); - - assert.strictEqual(registry.rules.quotes[0].specificity, 1); - assert.strictEqual(registry.rules.quotes[1].specificity, 2); - assert.strictEqual(registry.rules.quotes[6].specificity, 3); - }); - - it("should initially leave the errorCount as undefined", () => { - const registry = new autoconfig.Registry(rulesConfig); - - assert.isUndefined(registry.rules.quotes[0].errorCount); - assert.isUndefined(registry.rules.quotes[1].errorCount); - assert.isUndefined(registry.rules.quotes[6].errorCount); - }); - - describe("populateFromCoreRules()", () => { - - it("should add core rules to registry", () => { - const registry = new autoconfig.Registry(); - - registry.populateFromCoreRules(); - const finalRuleCount = Object.keys(registry.rules).length; - - assert(finalRuleCount > 0); - assert.include(Object.keys(registry.rules), "eqeqeq"); - }); - - it("should not add deprecated rules", () => { - const registry = new autoconfig.Registry(); - - registry.populateFromCoreRules(); - - const { rules } = registry; - - assert.notProperty(rules, "id-blacklist"); - assert.notProperty(rules, "no-negated-in-lhs"); - assert.notProperty(rules, "no-process-exit"); - assert.notProperty(rules, "no-spaced-func"); - assert.notProperty(rules, "prefer-reflect"); - }); - - it("should not add duplicate rules", () => { - const registry = new autoconfig.Registry(rulesConfig); - - registry.populateFromCoreRules(); - const semiCount = Object.keys(registry.rules).filter(ruleId => ruleId === "semi").length; - - assert.strictEqual(semiCount, 1); - }); - }); - - describe("buildRuleSets()", () => { - let ruleSets; - - beforeEach(() => { - const registry = new autoconfig.Registry(rulesConfig); - - ruleSets = registry.buildRuleSets(); - }); - - it("should create an array of rule configuration sets", () => { - assert.isArray(ruleSets); - }); - - it("should include configs for each rule (at least for the first set)", () => { - assert.sameMembers(Object.keys(ruleSets[0]), ["semi", "semi-spacing", "quotes"]); - }); - - it("should create the first set from default rule configs (severity only)", () => { - assert.deepStrictEqual(ruleSets[0], { semi: SEVERITY, "semi-spacing": SEVERITY, quotes: SEVERITY }); - }); - - it("should create as many ruleSets as the highest number of configs in a rule", () => { - - // `quotes` has 7 possible configurations - assert.lengthOf(ruleSets, 7); - }); - }); - - describe("lintSourceCode()", () => { - let registry; - - beforeEach(() => { - const config = { ignore: false }; - const sourceCode = sourceCodeUtils.getSourceCodeOfFiles(SOURCE_CODE_FIXTURE_FILENAME, config); - - registry = new autoconfig.Registry(rulesConfig); - registry = registry.lintSourceCode(sourceCode, defaultOptions); - }); - - it("should populate the errorCount of all registryItems", () => { - const expectedRules = ["semi", "semi-spacing", "quotes"]; - - assert.sameMembers(Object.keys(registry.rules), expectedRules); - expectedRules.forEach(ruleId => { - assert(registry.rules[ruleId].length > 0); - registry.rules[ruleId].forEach(conf => { - assert.isNumber(conf.errorCount); - }); - }); - }); - - it("should correctly set the error count of configurations", () => { - assert.strictEqual(registry.rules.semi[0].config, SEVERITY); - assert.strictEqual(registry.rules.semi[0].errorCount, 0); - assert.deepStrictEqual(registry.rules.semi[2].config, [SEVERITY, "never"]); - assert.strictEqual(registry.rules.semi[2].errorCount, 3); - }); - - it("should respect inline eslint config comments (and not crash when they make linting errors)", () => { - const config = { ignore: false }; - const sourceCode = sourceCodeUtils.getSourceCodeOfFiles(CONFIG_COMMENTS_FILENAME, config); - const expectedRegistry = [ - { config: 2, specificity: 1, errorCount: 3 }, - { config: [2, "always"], specificity: 2, errorCount: 3 }, - { config: [2, "never"], specificity: 2, errorCount: 3 } - ]; - - registry = new autoconfig.Registry(rulesConfig); - registry = registry.lintSourceCode(sourceCode, defaultOptions); - - assert.deepStrictEqual(registry.rules.semi, expectedRegistry); - }); - }); - - describe("stripFailingConfigs()", () => { - let registry; - - beforeEach(() => { - const config = { ignore: false }; - const sourceCode = sourceCodeUtils.getSourceCodeOfFiles(SOURCE_CODE_FIXTURE_FILENAME, config); - - registry = new autoconfig.Registry(rulesConfig); - registry = registry.lintSourceCode(sourceCode, defaultOptions); - registry = registry.stripFailingConfigs(); - }); - - it("should remove all registryItems with a non-zero errorCount", () => { - assert.lengthOf(registry.rules.semi, 2); - assert.lengthOf(registry.rules["semi-spacing"], 3); - assert.lengthOf(registry.rules.quotes, 1); - registry.rules.semi.forEach(registryItem => { - assert.strictEqual(registryItem.errorCount, 0); - }); - registry.rules["semi-spacing"].forEach(registryItem => { - assert.strictEqual(registryItem.errorCount, 0); - }); - registry.rules.quotes.forEach(registryItem => { - assert.strictEqual(registryItem.errorCount, 0); - }); - }); - }); - - describe("getFailingRulesRegistry()", () => { - let failingRegistry; - - beforeEach(() => { - const config = { ignore: false }; - const sourceCode = sourceCodeUtils.getSourceCodeOfFiles(SOURCE_CODE_FIXTURE_FILENAME, config); - let registry = new autoconfig.Registry(errorRulesConfig); - - registry = registry.lintSourceCode(sourceCode, defaultOptions); - failingRegistry = registry.getFailingRulesRegistry(); - }); - - it("should return a registry with no registryItems with an errorCount of zero", () => { - const failingRules = Object.keys(failingRegistry.rules); - - assert.deepStrictEqual(failingRules, ["no-unused-vars"]); - assert.lengthOf(failingRegistry.rules["no-unused-vars"], 1); - assert(failingRegistry.rules["no-unused-vars"][0].errorCount > 0); - }); - }); - - describe("createConfig()", () => { - let createdConfig; - - beforeEach(() => { - const config = { ignore: false }; - const sourceCode = sourceCodeUtils.getSourceCodeOfFiles(SOURCE_CODE_FIXTURE_FILENAME, config); - let registry = new autoconfig.Registry(rulesConfig); - - registry = registry.lintSourceCode(sourceCode, defaultOptions); - registry = registry.stripFailingConfigs(); - createdConfig = registry.createConfig(); - }); - - it("should create a config with a rules property", () => { - assert.property(createdConfig, "rules"); - }); - - it("should add rules which have only one registryItem to the config", () => { - const configuredRules = Object.keys(createdConfig.rules); - - assert.deepStrictEqual(configuredRules, ["quotes"]); - }); - - it("should set the configuration of the rule to the registryItem's `config` value", () => { - assert.deepStrictEqual(createdConfig.rules.quotes, [2, "double", "avoid-escape"]); - }); - - it("should not care how many errors the config has", () => { - const config = { ignore: false }; - const sourceCode = sourceCodeUtils.getSourceCodeOfFiles(SOURCE_CODE_FIXTURE_FILENAME, config); - let registry = new autoconfig.Registry(errorRulesConfig); - - registry = registry.lintSourceCode(sourceCode, defaultOptions); - const failingRegistry = registry.getFailingRulesRegistry(); - - createdConfig = failingRegistry.createConfig(); - const configuredRules = Object.keys(createdConfig.rules); - - assert.deepStrictEqual(configuredRules, ["no-unused-vars"]); - }); - }); - - describe("filterBySpecificity()", () => { - let registry; - - beforeEach(() => { - registry = new autoconfig.Registry(rulesConfig); - }); - - it("should return a registry where all configs have a desired specificity", () => { - const filteredRegistry1 = registry.filterBySpecificity(1); - const filteredRegistry2 = registry.filterBySpecificity(2); - const filteredRegistry3 = registry.filterBySpecificity(3); - - assert.lengthOf(filteredRegistry1.rules.semi, 1); - assert.lengthOf(filteredRegistry1.rules["semi-spacing"], 1); - assert.lengthOf(filteredRegistry1.rules.quotes, 1); - assert.lengthOf(filteredRegistry2.rules.semi, 2); - assert.lengthOf(filteredRegistry2.rules["semi-spacing"], 4); - assert.lengthOf(filteredRegistry2.rules.quotes, 3); - assert.lengthOf(filteredRegistry3.rules.quotes, 3); - }); - }); - }); - - describe("extendFromRecommended()", () => { - it("should return a configuration which has `extends` key with Array type value", () => { - const oldConfig = { extends: [], rules: {} }; - const newConfig = autoconfig.extendFromRecommended(oldConfig); - - assert.exists(newConfig.extends); - assert.isArray(newConfig.extends); - }); - - it("should return a configuration which has array property `extends`", () => { - const oldConfig = { extends: [], rules: {} }; - const newConfig = autoconfig.extendFromRecommended(oldConfig); - - assert.include(newConfig.extends, "eslint:recommended"); - }); - - it("should return a configuration which preserves the previous extending configurations", () => { - const oldConfig = { extends: ["previous:configuration1", "previous:configuration2"], rules: {} }; - const newConfig = autoconfig.extendFromRecommended(oldConfig); - - assert.includeMembers(newConfig.extends, oldConfig.extends); - }); - - it("should return a configuration which has `eslint:recommended` at the first of `extends`", () => { - const oldConfig = { extends: ["previous:configuration1", "previous:configuration2"], rules: {} }; - const newConfig = autoconfig.extendFromRecommended(oldConfig); - const [firstExtendInNewConfig] = newConfig.extends; - - assert.strictEqual(firstExtendInNewConfig, "eslint:recommended"); - }); - - it("should return a configuration which not includes rules configured in `eslint:recommended`", () => { - const oldConfig = { extends: [], rules: { ...recommendedConfig.rules } }; - const newConfig = autoconfig.extendFromRecommended(oldConfig); - - assert.notInclude(newConfig.rules, oldConfig.rules); - }); - }); -}); diff --git a/tests/lib/init/config-file.js b/tests/lib/init/config-file.js deleted file mode 100644 index 50a9af1d5079..000000000000 --- a/tests/lib/init/config-file.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * @fileoverview Tests for ConfigFile - * @author Nicholas C. Zakas - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - sinon = require("sinon"), - path = require("path"), - yaml = require("js-yaml"), - espree = require("espree"), - ConfigFile = require("../../../lib/init/config-file"), - { CLIEngine } = require("../../../lib/cli-engine"); - -const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Helper function get easily get a path in the fixtures directory. - * @param {string} filepath The path to find in the fixtures directory. - * @returns {string} Full path in the fixtures directory. - * @private - */ -function getFixturePath(filepath) { - return path.resolve(__dirname, "../../fixtures/config-file", filepath); -} - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("ConfigFile", () => { - describe("write()", () => { - let config; - - beforeEach(() => { - config = { - env: { - browser: true, - node: true - }, - rules: { - quotes: 2, - semi: 1 - } - }; - }); - - afterEach(() => { - sinon.verifyAndRestore(); - }); - - [ - ["JavaScript", "foo.js", espree.parse], - ["JSON", "bar.json", JSON.parse], - ["YAML", "foo.yaml", yaml.load], - ["YML", "foo.yml", yaml.load] - ].forEach(([fileType, filename, validate]) => { - - it(`should write a file through fs when a ${fileType} path is passed`, () => { - const fakeFS = { - writeFileSync: () => {} - }; - - sinon.mock(fakeFS).expects("writeFileSync").withExactArgs( - filename, - sinon.match(value => !!validate(value)), - "utf8" - ); - - const StubbedConfigFile = proxyquire("../../../lib/init/config-file", { - fs: fakeFS - }); - - StubbedConfigFile.write(config, filename); - }); - - it("should include a newline character at EOF", () => { - const fakeFS = { - writeFileSync: () => {} - }; - - sinon.mock(fakeFS).expects("writeFileSync").withExactArgs( - filename, - sinon.match(value => value.endsWith("\n")), - "utf8" - ); - - const StubbedConfigFile = proxyquire("../../../lib/init/config-file", { - fs: fakeFS - }); - - StubbedConfigFile.write(config, filename); - }); - }); - - it("should make sure js config files match linting rules", () => { - const fakeFS = { - writeFileSync: () => {} - }; - - const singleQuoteConfig = { - rules: { - quotes: [2, "single"] - } - }; - - sinon.mock(fakeFS).expects("writeFileSync").withExactArgs( - "test-config.js", - sinon.match(value => !value.includes("\"")), - "utf8" - ); - - const StubbedConfigFile = proxyquire("../../../lib/init/config-file", { - fs: fakeFS - }); - - StubbedConfigFile.write(singleQuoteConfig, "test-config.js"); - }); - - it("should still write a js config file even if linting fails", () => { - const fakeFS = { - writeFileSync: () => {} - }; - const fakeCLIEngine = sinon.mock().withExactArgs(sinon.match({ - baseConfig: config, - fix: true, - useEslintrc: false - })); - - Object.defineProperties(fakeCLIEngine.prototype, Object.getOwnPropertyDescriptors(CLIEngine.prototype)); - sinon.stub(fakeCLIEngine.prototype, "executeOnText").throws(); - - sinon.mock(fakeFS).expects("writeFileSync").once(); - - const StubbedConfigFile = proxyquire("../../../lib/init/config-file", { - fs: fakeFS, - "../cli-engine": { CLIEngine: fakeCLIEngine } - }); - - assert.throws(() => { - StubbedConfigFile.write(config, "test-config.js"); - }); - }); - - it("should throw error if file extension is not valid", () => { - assert.throws(() => { - ConfigFile.write({}, getFixturePath("yaml/.eslintrc.class")); - }, /write to unknown file type/u); - }); - }); -}); diff --git a/tests/lib/init/config-initializer.js b/tests/lib/init/config-initializer.js deleted file mode 100644 index 81e4e52faaa9..000000000000 --- a/tests/lib/init/config-initializer.js +++ /dev/null @@ -1,577 +0,0 @@ -/** - * @fileoverview Tests for configInitializer. - * @author Ilya Volodin - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - fs = require("fs"), - path = require("path"), - os = require("os"), - sinon = require("sinon"), - sh = require("shelljs"), - espree = require("espree"), - autoconfig = require("../../../lib/init/autoconfig"), - npmUtils = require("../../../lib/init/npm-utils"); - -const originalDir = process.cwd(); -const proxyquire = require("proxyquire").noPreserveCache(); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -let answers = {}; -let pkgJSONContents = {}; -let pkgJSONPath = ""; - -describe("configInitializer", () => { - - let fixtureDir, - npmCheckStub, - npmInstallStub, - npmFetchPeerDependenciesStub, - init, - localESLintVersion = null; - - const log = { - info: sinon.spy(), - error: sinon.spy() - }; - const requireStubs = { - "../shared/logging": log, - "../shared/relative-module-resolver": { - resolve() { - if (localESLintVersion) { - return `local-eslint-${localESLintVersion}`; - } - throw new Error("Cannot find module"); - } - }, - "local-eslint-3.18.0": { linter: { version: "3.18.0" }, "@noCallThru": true }, - "local-eslint-3.19.0": { linter: { version: "3.19.0" }, "@noCallThru": true }, - "local-eslint-4.0.0": { linter: { version: "4.0.0" }, "@noCallThru": true } - }; - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - const filepath = path.join(fixtureDir, ...args); - - try { - return fs.realpathSync(filepath); - } catch { - return filepath; - } - } - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(() => { - fixtureDir = path.join(os.tmpdir(), "eslint/fixtures/config-initializer"); - sh.mkdir("-p", fixtureDir); - sh.cp("-r", "./tests/fixtures/config-initializer/.", fixtureDir); - fixtureDir = fs.realpathSync(fixtureDir); - }); - - beforeEach(() => { - npmInstallStub = sinon.stub(npmUtils, "installSyncSaveDev"); - npmCheckStub = sinon.stub(npmUtils, "checkDevDeps").callsFake(packages => packages.reduce((status, pkg) => { - status[pkg] = false; - return status; - }, {})); - npmFetchPeerDependenciesStub = sinon - .stub(npmUtils, "fetchPeerDependencies") - .returns({ - eslint: "^3.19.0", - "eslint-plugin-jsx-a11y": "^5.0.1", - "eslint-plugin-import": "^2.2.0", - "eslint-plugin-react": "^7.0.1" - }); - init = proxyquire("../../../lib/init/config-initializer", requireStubs); - }); - - afterEach(() => { - log.info.resetHistory(); - log.error.resetHistory(); - npmInstallStub.restore(); - npmCheckStub.restore(); - npmFetchPeerDependenciesStub.restore(); - }); - - after(() => { - sh.rm("-r", fixtureDir); - }); - - describe("processAnswers()", () => { - - describe("prompt", () => { - - beforeEach(() => { - answers = { - purpose: "style", - source: "prompt", - extendDefault: true, - indent: 2, - quotes: "single", - linebreak: "unix", - semi: true, - moduleType: "esm", - es6Globals: true, - env: ["browser"], - format: "JSON" - }; - }); - - it("should create default config", () => { - const config = init.processAnswers(answers); - - assert.deepStrictEqual(config.rules.indent, ["error", 2]); - assert.deepStrictEqual(config.rules.quotes, ["error", "single"]); - assert.deepStrictEqual(config.rules["linebreak-style"], ["error", "unix"]); - assert.deepStrictEqual(config.rules.semi, ["error", "always"]); - assert.strictEqual(config.env.es2021, true); - assert.strictEqual(config.parserOptions.ecmaVersion, espree.latestEcmaVersion); - assert.strictEqual(config.parserOptions.sourceType, "module"); - assert.strictEqual(config.env.browser, true); - assert.strictEqual(config.extends, "eslint:recommended"); - }); - - it("should disable semi", () => { - answers.semi = false; - const config = init.processAnswers(answers); - - assert.deepStrictEqual(config.rules.semi, ["error", "never"]); - }); - - it("should enable react plugin", () => { - answers.framework = "react"; - const config = init.processAnswers(answers); - - assert.strictEqual(config.parserOptions.ecmaFeatures.jsx, true); - assert.strictEqual(config.parserOptions.ecmaVersion, espree.latestEcmaVersion); - assert.deepStrictEqual(config.plugins, ["react"]); - }); - - it("should enable vue plugin", () => { - answers.framework = "vue"; - const config = init.processAnswers(answers); - - assert.strictEqual(config.parserOptions.ecmaVersion, espree.latestEcmaVersion); - assert.deepStrictEqual(config.plugins, ["vue"]); - assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential"]); - }); - - it("should enable typescript parser and plugin", () => { - answers.typescript = true; - const config = init.processAnswers(answers); - - assert.strictEqual(config.parser, "@typescript-eslint/parser"); - assert.deepStrictEqual(config.plugins, ["@typescript-eslint"]); - assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:@typescript-eslint/recommended"]); - }); - - it("should enable typescript parser and plugin with vue", () => { - answers.framework = "vue"; - answers.typescript = true; - const config = init.processAnswers(answers); - - assert.deepStrictEqual(config.extends, ["eslint:recommended", "plugin:vue/essential", "plugin:@typescript-eslint/recommended"]); - assert.strictEqual(config.parserOptions.parser, "@typescript-eslint/parser"); - assert.deepStrictEqual(config.plugins, ["vue", "@typescript-eslint"]); - }); - - it("should extend eslint:recommended", () => { - const config = init.processAnswers(answers); - - assert.strictEqual(config.extends, "eslint:recommended"); - }); - - it("should not use commonjs by default", () => { - const config = init.processAnswers(answers); - - assert.isUndefined(config.env.commonjs); - }); - - it("should use commonjs when set", () => { - answers.moduleType = "commonjs"; - const config = init.processAnswers(answers); - - assert.isTrue(config.env.commonjs); - }); - }); - - describe("guide", () => { - it("should support the google style guide", () => { - const config = { extends: "google" }; - const modules = init.getModulesList(config); - - assert.deepStrictEqual(config, { extends: "google", installedESLint: true }); - assert.include(modules, "eslint-config-google@latest"); - }); - - it("should support the airbnb style guide", () => { - const config = { extends: "airbnb" }; - const modules = init.getModulesList(config); - - assert.deepStrictEqual(config, { extends: "airbnb", installedESLint: true }); - assert.include(modules, "eslint-config-airbnb@latest"); - }); - - it("should support the airbnb base style guide", () => { - const config = { extends: "airbnb-base" }; - const modules = init.getModulesList(config); - - assert.deepStrictEqual(config, { extends: "airbnb-base", installedESLint: true }); - assert.include(modules, "eslint-config-airbnb-base@latest"); - }); - - it("should support the standard style guide", () => { - const config = { extends: "standard" }; - const modules = init.getModulesList(config); - - assert.deepStrictEqual(config, { extends: "standard", installedESLint: true }); - assert.include(modules, "eslint-config-standard@latest"); - }); - - it("should support the xo style guide", () => { - const config = { extends: "xo" }; - const modules = init.getModulesList(config); - - assert.deepStrictEqual(config, { extends: "xo", installedESLint: true }); - assert.include(modules, "eslint-config-xo@latest"); - }); - - it("should install required sharable config", () => { - const config = { extends: "google" }; - - init.installModules(init.getModulesList(config)); - assert(npmInstallStub.calledOnce); - assert(npmInstallStub.firstCall.args[0].some(name => name.startsWith("eslint-config-google@"))); - }); - - it("should install ESLint if not installed locally", () => { - const config = { extends: "google" }; - - init.installModules(init.getModulesList(config)); - assert(npmInstallStub.calledOnce); - assert(npmInstallStub.firstCall.args[0].some(name => name.startsWith("eslint@"))); - }); - - it("should install peerDependencies of the sharable config", () => { - const config = { extends: "airbnb" }; - - init.installModules(init.getModulesList(config)); - - assert(npmFetchPeerDependenciesStub.calledOnce); - assert(npmFetchPeerDependenciesStub.firstCall.args[0] === "eslint-config-airbnb@latest"); - assert(npmInstallStub.calledOnce); - assert.deepStrictEqual( - npmInstallStub.firstCall.args[0], - [ - "eslint-config-airbnb@latest", - "eslint@^3.19.0", - "eslint-plugin-jsx-a11y@^5.0.1", - "eslint-plugin-import@^2.2.0", - "eslint-plugin-react@^7.0.1" - ] - ); - }); - - describe("hasESLintVersionConflict (Note: peerDependencies always `eslint: \"^3.19.0\"` by stubs)", () => { - describe("if local ESLint is not found,", () => { - before(() => { - localESLintVersion = null; - }); - - it("should return false.", () => { - const result = init.hasESLintVersionConflict({ styleguide: "airbnb" }); - - assert.strictEqual(result, false); - }); - }); - - describe("if local ESLint is 3.19.0,", () => { - before(() => { - localESLintVersion = "3.19.0"; - }); - - it("should return false.", () => { - const result = init.hasESLintVersionConflict({ styleguide: "airbnb" }); - - assert.strictEqual(result, false); - }); - }); - - describe("if local ESLint is 4.0.0,", () => { - before(() => { - localESLintVersion = "4.0.0"; - }); - - it("should return true.", () => { - const result = init.hasESLintVersionConflict({ styleguide: "airbnb" }); - - assert.strictEqual(result, true); - }); - }); - - describe("if local ESLint is 3.18.0,", () => { - before(() => { - localESLintVersion = "3.18.0"; - }); - - it("should return true.", () => { - const result = init.hasESLintVersionConflict({ styleguide: "airbnb" }); - - assert.strictEqual(result, true); - }); - }); - }); - - it("should support the standard style guide with Vue.js", () => { - const config = { - plugins: ["vue"], - extends: ["plugin:vue/essential", "standard"] - }; - const modules = init.getModulesList(config); - - assert.include(modules, "eslint-plugin-vue@latest"); - assert.include(modules, "eslint-config-standard@latest"); - }); - - it("should support custom parser", () => { - const config = { - parser: "@typescript-eslint/parser" - }; - const modules = init.getModulesList(config); - - assert.include(modules, "@typescript-eslint/parser@latest"); - }); - - it("should support custom parser with Vue.js", () => { - const config = { - - // We should declare the parser at `parserOptions` when using with `eslint-plugin-vue`. - parserOptions: { - parser: "@typescript-eslint/parser" - } - }; - const modules = init.getModulesList(config); - - assert.include(modules, "@typescript-eslint/parser@latest"); - }); - }); - - describe("auto", () => { - const completeSpy = sinon.spy(); - let config; - - before(() => { - const patterns = [ - getFixturePath("lib"), - getFixturePath("tests") - ].join(" "); - - answers = { - purpose: "style", - source: "auto", - patterns, - env: ["browser"], - format: "JSON" - }; - - sinon.stub(console, "log"); // necessary to replace, because of progress bar - - process.chdir(fixtureDir); - config = init.processAnswers(answers); - sinon.restore(); - }); - - after(() => { - sinon.restore(); - }); - - afterEach(() => { - process.chdir(originalDir); - sinon.restore(); - }); - - it("should create a config", () => { - assert.isTrue(completeSpy.notCalled); - assert.ok(config); - }); - - it("should create the config based on examined files", () => { - assert.deepStrictEqual(config.rules.quotes, ["error", "double"]); - assert.strictEqual(config.rules.semi, "off"); - }); - - it("should extend and not disable recommended rules", () => { - assert.strictEqual(config.extends, "eslint:recommended"); - assert.notProperty(config.rules, "no-debugger"); - }); - - it("should not include deprecated rules", () => { - assert.notProperty(config.rules, "id-blacklist"); - assert.notProperty(config.rules, "no-negated-in-lhs"); - assert.notProperty(config.rules, "no-process-exit"); - assert.notProperty(config.rules, "no-spaced-func"); - assert.notProperty(config.rules, "prefer-reflect"); - }); - - it("should support new ES features if using later ES version", () => { - const filename = getFixturePath("new-es-features"); - - answers.patterns = filename; - answers.ecmaVersion = 2017; - process.chdir(fixtureDir); - config = init.processAnswers(answers); - }); - - it("should throw on fatal parsing error", () => { - const filename = getFixturePath("parse-error"); - - sinon.stub(autoconfig, "extendFromRecommended"); - answers.patterns = filename; - process.chdir(fixtureDir); - assert.throws(() => { - config = init.processAnswers(answers); - }, "Parsing error: Unexpected token ;"); - }); - - it("should throw if no files are matched from patterns", () => { - sinon.stub(autoconfig, "extendFromRecommended"); - answers.patterns = "not-a-real-filename"; - process.chdir(fixtureDir); - assert.throws(() => { - config = init.processAnswers(answers); - }, "No files matching 'not-a-real-filename' were found."); - }); - }); - }); - - describe("writeFile()", () => { - - beforeEach(() => { - answers = { - purpose: "style", - source: "prompt", - extendDefault: true, - indent: 2, - quotes: "single", - linebreak: "unix", - semi: true, - moduleType: "esm", - es6Globals: true, - env: ["browser"], - format: "JSON" - }; - - pkgJSONContents = { - name: "config-initializer", - version: "1.0.0" - }; - - process.chdir(fixtureDir); - - pkgJSONPath = path.resolve(fixtureDir, "package.json"); - }); - - afterEach(() => { - process.chdir(originalDir); - }); - - it("should create .eslintrc.json", () => { - const config = init.processAnswers(answers); - const filePath = path.resolve(fixtureDir, ".eslintrc.json"); - - fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents)); - - init.writeFile(config, answers.format); - - assert.isTrue(fs.existsSync(filePath)); - - fs.unlinkSync(filePath); - fs.unlinkSync(pkgJSONPath); - }); - - it("should create .eslintrc.js", () => { - answers.format = "JavaScript"; - - const config = init.processAnswers(answers); - const filePath = path.resolve(fixtureDir, ".eslintrc.js"); - - fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents)); - - init.writeFile(config, answers.format); - - assert.isTrue(fs.existsSync(filePath)); - - fs.unlinkSync(filePath); - fs.unlinkSync(pkgJSONPath); - }); - - it("should create .eslintrc.yml", () => { - answers.format = "YAML"; - - const config = init.processAnswers(answers); - const filePath = path.resolve(fixtureDir, ".eslintrc.yml"); - - fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents)); - - init.writeFile(config, answers.format); - - assert.isTrue(fs.existsSync(filePath)); - - fs.unlinkSync(filePath); - fs.unlinkSync(pkgJSONPath); - }); - - // For https://github.com/eslint/eslint/issues/14137 - it("should create .eslintrc.cjs", () => { - answers.format = "JavaScript"; - - // create package.json with "type": "module" - pkgJSONContents.type = "module"; - - fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents)); - - const config = init.processAnswers(answers); - const filePath = path.resolve(fixtureDir, ".eslintrc.cjs"); - - init.writeFile(config, answers.format); - - assert.isTrue(fs.existsSync(filePath)); - - fs.unlinkSync(filePath); - fs.unlinkSync(pkgJSONPath); - }); - - it("should create .eslintrc.json even with type: 'module'", () => { - answers.format = "JSON"; - - // create package.json with "type": "module" - pkgJSONContents.type = "module"; - - fs.writeFileSync(pkgJSONPath, JSON.stringify(pkgJSONContents)); - - const config = init.processAnswers(answers); - const filePath = path.resolve(fixtureDir, ".eslintrc.json"); - - init.writeFile(config, answers.format); - - assert.isTrue(fs.existsSync(filePath)); - - fs.unlinkSync(filePath); - fs.unlinkSync(pkgJSONPath); - }); - }); -}); diff --git a/tests/lib/init/config-rule.js b/tests/lib/init/config-rule.js deleted file mode 100644 index 425317dd2c8a..000000000000 --- a/tests/lib/init/config-rule.js +++ /dev/null @@ -1,329 +0,0 @@ -/** - * @fileoverview Tests for ConfigOps - * @author Ian VanSchooten - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const assert = require("chai").assert, - ConfigRule = require("../../../lib/init/config-rule"), - builtInRules = require("../../../lib/rules"), - schema = require("../../fixtures/config-rule/schemas"); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -const SEVERITY = 2; - -describe("ConfigRule", () => { - - describe("generateConfigsFromSchema()", () => { - let actualConfigs; - - it("should create a config with only severity for an empty schema", () => { - actualConfigs = ConfigRule.generateConfigsFromSchema([]); - assert.deepStrictEqual(actualConfigs, [SEVERITY]); - }); - - it("should create a config with only severity with no arguments", () => { - actualConfigs = ConfigRule.generateConfigsFromSchema(); - assert.deepStrictEqual(actualConfigs, [SEVERITY]); - }); - - describe("for a single enum schema", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.enum); - }); - - it("should create an array of configs", () => { - assert.isArray(actualConfigs); - assert.strictEqual(actualConfigs.length, 3); - }); - - it("should include the error severity (2) without options as the first config", () => { - assert.strictEqual(actualConfigs[0], SEVERITY); - }); - - it("should set all configs to error severity (2)", () => { - actualConfigs.forEach(actualConfig => { - if (Array.isArray(actualConfig)) { - assert.strictEqual(actualConfig[0], SEVERITY); - } - }); - }); - - it("should return configs with each enumerated value in the schema", () => { - assert.sameDeepMembers(actualConfigs, [SEVERITY, [SEVERITY, "always"], [SEVERITY, "never"]]); - }); - }); - - describe("for a object schema with a single enum property", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithEnum); - }); - - it("should return configs with option objects", () => { - - // Skip first config (severity only) - actualConfigs.slice(1).forEach(actualConfig => { - const actualConfigOption = actualConfig[1]; // severity is first element, option is second - - assert.isObject(actualConfigOption); - }); - }); - - it("should use the object property name from the schema", () => { - const propName = "enumProperty"; - - assert.strictEqual(actualConfigs.length, 3); - actualConfigs.slice(1).forEach(actualConfig => { - const actualConfigOption = actualConfig[1]; - - assert.property(actualConfigOption, propName); - }); - }); - - it("should have each enum as option object values", () => { - const propName = "enumProperty", - actualValues = []; - - actualConfigs.slice(1).forEach(actualConfig => { - const configOption = actualConfig[1]; - - actualValues.push(configOption[propName]); - }); - assert.sameMembers(actualValues, ["always", "never"]); - }); - }); - - describe("for a object schema with a multiple enum properties", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithMultipleEnums); - }); - - it("should create configs for all properties in each config", () => { - const expectedProperties = ["firstEnum", "anotherEnum"]; - - assert.strictEqual(actualConfigs.length, 7); - actualConfigs.slice(1).forEach(actualConfig => { - const configOption = actualConfig[1]; - const actualProperties = Object.keys(configOption); - - assert.sameMembers(actualProperties, expectedProperties); - }); - }); - - it("should create configs for every possible combination", () => { - const expectedConfigs = [ - { firstEnum: "always", anotherEnum: "var" }, - { firstEnum: "always", anotherEnum: "let" }, - { firstEnum: "always", anotherEnum: "const" }, - { firstEnum: "never", anotherEnum: "var" }, - { firstEnum: "never", anotherEnum: "let" }, - { firstEnum: "never", anotherEnum: "const" } - ]; - const actualConfigOptions = actualConfigs.slice(1).map(actualConfig => actualConfig[1]); - - assert.sameDeepMembers(actualConfigOptions, expectedConfigs); - }); - - }); - - describe("for a object schema with a single boolean property", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithBool); - }); - - it("should return configs with option objects", () => { - assert.strictEqual(actualConfigs.length, 3); - actualConfigs.slice(1).forEach(actualConfig => { - const actualConfigOption = actualConfig[1]; - - assert.isObject(actualConfigOption); - }); - }); - - it("should use the object property name from the schema", () => { - const propName = "boolProperty"; - - assert.strictEqual(actualConfigs.length, 3); - actualConfigs.slice(1).forEach(actualConfig => { - const actualConfigOption = actualConfig[1]; - - assert.property(actualConfigOption, propName); - }); - }); - - it("should include both true and false configs", () => { - const propName = "boolProperty", - actualValues = []; - - actualConfigs.slice(1).forEach(actualConfig => { - const configOption = actualConfig[1]; - - actualValues.push(configOption[propName]); - }); - assert.sameMembers(actualValues, [true, false]); - }); - }); - - describe("for a object schema with a multiple bool properties", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.objectWithMultipleBools); - }); - - it("should create configs for all properties in each config", () => { - const expectedProperties = ["firstBool", "anotherBool"]; - - assert.strictEqual(actualConfigs.length, 5); - actualConfigs.slice(1).forEach(config => { - const configOption = config[1]; - const actualProperties = Object.keys(configOption); - - assert.sameMembers(actualProperties, expectedProperties); - }); - }); - - it("should create configs for every possible combination", () => { - const expectedConfigOptions = [ - { firstBool: true, anotherBool: true }, - { firstBool: true, anotherBool: false }, - { firstBool: false, anotherBool: true }, - { firstBool: false, anotherBool: false } - ]; - const actualConfigOptions = actualConfigs.slice(1).map(config => config[1]); - - assert.sameDeepMembers(actualConfigOptions, expectedConfigOptions); - }); - }); - - describe("for a schema with an enum and an object", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedEnumObject); - }); - - it("should create configs with only the enum values", () => { - assert.strictEqual(actualConfigs[1].length, 2); - assert.strictEqual(actualConfigs[2].length, 2); - const actualOptions = [actualConfigs[1][1], actualConfigs[2][1]]; - - assert.sameMembers(actualOptions, ["always", "never"]); - }); - - it("should create configs with a string and an object", () => { - assert.strictEqual(actualConfigs.length, 7); - actualConfigs.slice(3).forEach(config => { - assert.isString(config[1]); - assert.isObject(config[2]); - }); - }); - }); - - describe("for a schema with an enum followed by an object with no usable properties", () => { - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedEnumObjectWithNothing); - }); - - it("should create config only for the enum", () => { - const expectedConfigs = [2, [2, "always"], [2, "never"]]; - - assert.sameDeepMembers(actualConfigs, expectedConfigs); - }); - }); - - describe("for a schema with an enum preceded by an object with no usable properties", () => { - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedObjectWithNothingEnum); - }); - - it("should not create a config for the enum", () => { - const expectedConfigs = [2]; - - assert.sameDeepMembers(actualConfigs, expectedConfigs); - }); - }); - - describe("for a schema with an enum preceded by a string", () => { - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.mixedStringEnum); - }); - - it("should not create a config for the enum", () => { - const expectedConfigs = [2]; - - assert.sameDeepMembers(actualConfigs, expectedConfigs); - }); - }); - - describe("for a schema with oneOf", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.oneOf); - }); - - it("should create a set of configs", () => { - assert.isArray(actualConfigs); - }); - }); - - describe("for a schema with nested objects", () => { - - before(() => { - actualConfigs = ConfigRule.generateConfigsFromSchema(schema.nestedObjects); - }); - - it("should create a set of configs", () => { - assert.isArray(actualConfigs); - }); - }); - }); - - describe("createCoreRuleConfigs()", () => { - - const rulesConfig = ConfigRule.createCoreRuleConfigs(); - - it("should create a rulesConfig containing all core rules", () => { - const - expectedRules = Array.from(builtInRules.keys()), - actualRules = Object.keys(rulesConfig); - - assert.sameMembers(actualRules, expectedRules); - }); - - it("should allow to ignore deprecated rules", () => { - const expectedRules = Array.from(builtInRules.entries()) - .filter(([, rule]) => { - const isDeprecated = (typeof rule === "function") ? rule.deprecated : rule.meta.deprecated; - - return !isDeprecated; - }) - .map(([id]) => id), - actualRules = Object.keys(ConfigRule.createCoreRuleConfigs(true)); - - assert.sameMembers(actualRules, expectedRules); - - // Make sure it doesn't contain deprecated rules. - assert.notInclude(actualRules, "newline-after-var"); - }); - - it("should create arrays of configs for rules", () => { - assert.isArray(rulesConfig.quotes); - assert.include(rulesConfig.quotes, 2); - }); - - it("should create configs for rules with meta", () => { - assert(rulesConfig["accessor-pairs"].length > 1); - }); - }); -}); diff --git a/tests/lib/init/npm-utils.js b/tests/lib/init/npm-utils.js deleted file mode 100644 index 8465796a367f..000000000000 --- a/tests/lib/init/npm-utils.js +++ /dev/null @@ -1,228 +0,0 @@ -/** - * @fileoverview Tests for rule fixer. - * @author Ian VanSchooten - */ -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const - assert = require("chai").assert, - spawn = require("cross-spawn"), - sinon = require("sinon"), - npmUtils = require("../../../lib/init/npm-utils"), - log = require("../../../lib/shared/logging"), - { defineInMemoryFs } = require("../../_utils"); - -const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); - -//------------------------------------------------------------------------------ -// Helpers -//------------------------------------------------------------------------------ - -/** - * Import `npm-utils` with the in-memory file system. - * @param {Object} files The file definitions. - * @returns {Object} `npm-utils`. - */ -function requireNpmUtilsWithInMemoryFileSystem(files) { - const fs = defineInMemoryFs({ files }); - - return proxyquire("../../../lib/init/npm-utils", { fs }); -} - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("npmUtils", () => { - afterEach(() => { - sinon.verifyAndRestore(); - }); - - describe("checkDevDeps()", () => { - let installStatus; - - before(() => { - installStatus = npmUtils.checkDevDeps(["debug", "mocha", "notarealpackage", "jshint"]); - }); - - it("should not find a direct dependency of the project", () => { - assert.isFalse(installStatus.debug); - }); - - it("should find a dev dependency of the project", () => { - assert.isTrue(installStatus.mocha); - }); - - it("should not find non-dependencies", () => { - assert.isFalse(installStatus.notarealpackage); - }); - - it("should not find nested dependencies", () => { - assert.isFalse(installStatus.jshint); - }); - - it("should return false for a single, non-existent package", () => { - installStatus = npmUtils.checkDevDeps(["notarealpackage"]); - assert.isFalse(installStatus.notarealpackage); - }); - - it("should handle missing devDependencies key", () => { - const stubbedNpmUtils = requireNpmUtilsWithInMemoryFileSystem({ - "package.json": JSON.stringify({ private: true, dependencies: {} }) - }); - - // Should not throw. - stubbedNpmUtils.checkDevDeps(["some-package"]); - }); - - it("should throw with message when parsing invalid package.json", () => { - const stubbedNpmUtils = requireNpmUtilsWithInMemoryFileSystem({ - "package.json": "{ \"not: \"valid json\" }" - }); - - assert.throws(() => { - try { - stubbedNpmUtils.checkDevDeps(["some-package"]); - } catch (error) { - assert.strictEqual(error.messageTemplate, "failed-to-read-json"); - throw error; - } - }, "SyntaxError: Unexpected token v"); - }); - }); - - describe("checkDeps()", () => { - let installStatus; - - before(() => { - installStatus = npmUtils.checkDeps(["debug", "mocha", "notarealpackage", "jshint"]); - }); - - it("should find a direct dependency of the project", () => { - assert.isTrue(installStatus.debug); - }); - - it("should not find a dev dependency of the project", () => { - assert.isFalse(installStatus.mocha); - }); - - it("should not find non-dependencies", () => { - assert.isFalse(installStatus.notarealpackage); - }); - - it("should not find nested dependencies", () => { - assert.isFalse(installStatus.jshint); - }); - - it("should return false for a single, non-existent package", () => { - installStatus = npmUtils.checkDeps(["notarealpackage"]); - assert.isFalse(installStatus.notarealpackage); - }); - - it("should throw if no package.json can be found", () => { - assert.throws(() => { - installStatus = npmUtils.checkDeps(["notarealpackage"], "/fakepath"); - }, "Could not find a package.json file"); - }); - - it("should handle missing dependencies key", () => { - const stubbedNpmUtils = requireNpmUtilsWithInMemoryFileSystem({ - "package.json": JSON.stringify({ private: true, devDependencies: {} }) - }); - - // Should not throw. - stubbedNpmUtils.checkDeps(["some-package"]); - }); - - it("should throw with message when parsing invalid package.json", () => { - const stubbedNpmUtils = requireNpmUtilsWithInMemoryFileSystem({ - "package.json": "{ \"not: \"valid json\" }" - }); - - assert.throws(() => { - try { - stubbedNpmUtils.checkDeps(["some-package"]); - } catch (error) { - assert.strictEqual(error.messageTemplate, "failed-to-read-json"); - throw error; - } - }, "SyntaxError: Unexpected token v"); - }); - }); - - describe("checkPackageJson()", () => { - it("should return true if package.json exists", () => { - const stubbedNpmUtils = requireNpmUtilsWithInMemoryFileSystem({ - "package.json": "{ \"file\": \"contents\" }" - }); - - assert.strictEqual(stubbedNpmUtils.checkPackageJson(), true); - }); - - it("should return false if package.json does not exist", () => { - const stubbedNpmUtils = requireNpmUtilsWithInMemoryFileSystem({}); - - assert.strictEqual(stubbedNpmUtils.checkPackageJson(), false); - }); - }); - - describe("installSyncSaveDev()", () => { - it("should invoke npm to install a single desired package", () => { - const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); - - npmUtils.installSyncSaveDev("desired-package"); - assert(stub.calledOnce); - assert.strictEqual(stub.firstCall.args[0], "npm"); - assert.deepStrictEqual(stub.firstCall.args[1], ["i", "--save-dev", "desired-package"]); - stub.restore(); - }); - - it("should accept an array of packages to install", () => { - const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); - - npmUtils.installSyncSaveDev(["first-package", "second-package"]); - assert(stub.calledOnce); - assert.strictEqual(stub.firstCall.args[0], "npm"); - assert.deepStrictEqual(stub.firstCall.args[1], ["i", "--save-dev", "first-package", "second-package"]); - stub.restore(); - }); - - it("should log an error message if npm throws ENOENT error", () => { - const logErrorStub = sinon.stub(log, "error"); - const npmUtilsStub = sinon.stub(spawn, "sync").returns({ error: { code: "ENOENT" } }); - - npmUtils.installSyncSaveDev("some-package"); - - assert(logErrorStub.calledOnce); - - logErrorStub.restore(); - npmUtilsStub.restore(); - }); - }); - - describe("fetchPeerDependencies()", () => { - it("should execute 'npm show --json peerDependencies' command", () => { - const stub = sinon.stub(spawn, "sync").returns({ stdout: "" }); - - npmUtils.fetchPeerDependencies("desired-package"); - assert(stub.calledOnce); - assert.strictEqual(stub.firstCall.args[0], "npm"); - assert.deepStrictEqual(stub.firstCall.args[1], ["show", "--json", "desired-package", "peerDependencies"]); - stub.restore(); - }); - - it("should return null if npm throws ENOENT error", () => { - const stub = sinon.stub(spawn, "sync").returns({ error: { code: "ENOENT" } }); - - const peerDependencies = npmUtils.fetchPeerDependencies("desired-package"); - - assert.isNull(peerDependencies); - - stub.restore(); - }); - }); -}); diff --git a/tests/lib/init/source-code-utils.js b/tests/lib/init/source-code-utils.js deleted file mode 100644 index 994d23d31688..000000000000 --- a/tests/lib/init/source-code-utils.js +++ /dev/null @@ -1,250 +0,0 @@ -/** - * @fileoverview Tests for source-code-util. - * @author Ian VanSchooten - */ - -"use strict"; - -//------------------------------------------------------------------------------ -// Requirements -//------------------------------------------------------------------------------ - -const path = require("path"), - fs = require("fs"), - os = require("os"), - assert = require("chai").assert, - sinon = require("sinon"), - sh = require("shelljs"), - { SourceCode } = require("../../../lib/source-code"); - -const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); -const originalDir = process.cwd(); - -//------------------------------------------------------------------------------ -// Tests -//------------------------------------------------------------------------------ - -describe("SourceCodeUtil", () => { - - let fixtureDir, - getSourceCodeOfFiles; - - /** - * Returns the path inside of the fixture directory. - * @param {...string} args file path segments. - * @returns {string} The path inside the fixture directory. - * @private - */ - function getFixturePath(...args) { - let filepath = path.join(fixtureDir, ...args); - - try { - filepath = fs.realpathSync(filepath); - return filepath; - } catch { - return filepath; - } - } - - const log = { - info: sinon.spy(), - error: sinon.spy() - }; - const requireStubs = { - "../logging": log - }; - - // copy into clean area so as not to get "infected" by this project's .eslintrc files - before(() => { - fixtureDir = `${os.tmpdir()}/eslint/fixtures/source-code-util`; - sh.mkdir("-p", fixtureDir); - sh.cp("-r", "./tests/fixtures/source-code-util/.", fixtureDir); - fixtureDir = fs.realpathSync(fixtureDir); - }); - - beforeEach(() => { - getSourceCodeOfFiles = proxyquire("../../../lib/init/source-code-utils", requireStubs).getSourceCodeOfFiles; - }); - - afterEach(() => { - log.info.resetHistory(); - log.error.resetHistory(); - }); - - after(() => { - sh.rm("-r", fixtureDir); - }); - - describe("getSourceCodeOfFiles()", () => { - - it("should handle single string filename arguments", () => { - const filename = getFixturePath("foo.js"); - const sourceCode = getSourceCodeOfFiles(filename, { cwd: fixtureDir }); - - assert.isObject(sourceCode); - }); - - it("should accept an array of string filenames", () => { - const fooFilename = getFixturePath("foo.js"); - const barFilename = getFixturePath("bar.js"); - const sourceCode = getSourceCodeOfFiles([fooFilename, barFilename], { cwd: fixtureDir }); - - assert.isObject(sourceCode); - }); - - it("should accept a glob argument", () => { - const glob = getFixturePath("*.js"); - const filename = getFixturePath("foo.js"); - const sourceCode = getSourceCodeOfFiles(glob, { cwd: fixtureDir }); - - assert.isObject(sourceCode); - assert.property(sourceCode, filename); - }); - - it("should accept a relative filename", () => { - const filename = "foo.js"; - const sourceCode = getSourceCodeOfFiles(filename, { cwd: fixtureDir }); - - assert.isObject(sourceCode); - assert.property(sourceCode, getFixturePath(filename)); - }); - - it("should accept a relative path to a file in a parent directory", () => { - const filename = "../foo.js"; - const sourceCode = getSourceCodeOfFiles(filename, { cwd: getFixturePath("nested") }); - - assert.isObject(sourceCode); - assert.property(sourceCode, getFixturePath("foo.js")); - }); - - it("should accept a callback", () => { - const filename = getFixturePath("foo.js"); - const spy = sinon.spy(); - - process.chdir(fixtureDir); - getSourceCodeOfFiles(filename, {}, spy); - process.chdir(originalDir); - assert(spy.calledOnce); - }); - - it("should call the callback with total number of files being processed", () => { - const filename = getFixturePath("foo.js"); - const spy = sinon.spy(); - - process.chdir(fixtureDir); - getSourceCodeOfFiles(filename, {}, spy); - process.chdir(originalDir); - assert.strictEqual(spy.firstCall.args[0], 1); - }); - - it("should create an object with located filenames as keys", () => { - const fooFilename = getFixturePath("foo.js"); - const barFilename = getFixturePath("bar.js"); - const sourceCode = getSourceCodeOfFiles([fooFilename, barFilename], { cwd: fixtureDir }); - - assert.property(sourceCode, fooFilename); - assert.property(sourceCode, barFilename); - }); - - it("should should not include non-existent filenames in results", () => { - const filename = getFixturePath("missing.js"); - - assert.throws(() => { - getSourceCodeOfFiles(filename, { cwd: fixtureDir }); - }, `No files matching '${filename}' were found.`); - }); - - it("should throw for files with parsing errors", () => { - const filename = getFixturePath("parse-error", "parse-error.js"); - - assert.throw(() => { - getSourceCodeOfFiles(filename, { cwd: fixtureDir }); - }, /Parsing error: Unexpected token ;/u); - - }); - - it("should obtain the sourceCode of a file", () => { - const filename = getFixturePath("foo.js"); - const sourceCode = getSourceCodeOfFiles(filename, { cwd: fixtureDir }); - - assert.isObject(sourceCode); - assert.instanceOf(sourceCode[filename], SourceCode); - }); - - it("should obtain the sourceCode of JSX files", () => { - const filename = getFixturePath("jsx", "foo.jsx"); - const options = { - cwd: fixtureDir, - parserOptions: { - ecmaFeatures: { - jsx: true - } - } - }; - const sourceCode = getSourceCodeOfFiles(filename, options); - - assert.isObject(sourceCode); - assert.instanceOf(sourceCode[filename], SourceCode); - }); - - it("should honor .eslintignore files by default", () => { - const glob = getFixturePath("*.js"); - const unignoredFilename = getFixturePath("foo.js"); - const ignoredFilename = getFixturePath("ignored.js"); - const sourceCode = getSourceCodeOfFiles(glob, { cwd: fixtureDir }); - - assert.property(sourceCode, unignoredFilename); - assert.notProperty(sourceCode, ignoredFilename); - }); - - it("should obtain the sourceCode of all files in a specified folder", () => { - const folder = getFixturePath("nested"); - const fooFile = getFixturePath("nested/foo.js"); - const barFile = getFixturePath("nested/bar.js"); - const sourceCode = getSourceCodeOfFiles(folder, { cwd: fixtureDir }); - - assert.strictEqual(Object.keys(sourceCode).length, 2); - assert.instanceOf(sourceCode[fooFile], SourceCode); - assert.instanceOf(sourceCode[barFile], SourceCode); - }); - - it("should accept cli options", () => { - const pattern = getFixturePath("ext"); - const abcFile = getFixturePath("ext/foo.abc"); - const cliOptions = { extensions: [".abc"], cwd: fixtureDir }; - const sourceCode = getSourceCodeOfFiles(pattern, cliOptions); - - assert.strictEqual(Object.keys(sourceCode).length, 1); - assert.instanceOf(sourceCode[abcFile], SourceCode); - }); - - it("should execute the callback function, if provided", () => { - const callback = sinon.spy(); - const filename = getFixturePath("foo.js"); - - getSourceCodeOfFiles(filename, { cwd: fixtureDir }, callback); - assert(callback.calledOnce); - }); - - it("should execute callback function once per file", () => { - const callback = sinon.spy(); - const fooFilename = getFixturePath("foo.js"); - const barFilename = getFixturePath("bar.js"); - - getSourceCodeOfFiles([fooFilename, barFilename], { cwd: fixtureDir }, callback); - assert.strictEqual(callback.callCount, 2); - }); - - it("should call callback function with total number of files with sourceCode", () => { - const callback = sinon.spy(); - const firstFn = getFixturePath("foo.js"); - const secondFn = getFixturePath("bar.js"); - const thirdFn = getFixturePath("nested/foo.js"); - - getSourceCodeOfFiles([firstFn, secondFn, thirdFn], { cwd: fixtureDir }, callback); - assert(callback.calledWith(3)); - }); - - }); - -}); diff --git a/tests/tools/eslint-fuzzer.js b/tests/tools/eslint-fuzzer.js index 4723befa4e28..1c894b99595b 100644 --- a/tests/tools/eslint-fuzzer.js +++ b/tests/tools/eslint-fuzzer.js @@ -8,7 +8,7 @@ const assert = require("chai").assert; const eslint = require("../.."); const espree = require("espree"); const sinon = require("sinon"); -const configRule = require("../../lib/init/config-rule"); +const configRule = require("../../tools/config-rule"); //------------------------------------------------------------------------------ // Tests diff --git a/lib/init/config-rule.js b/tools/config-rule.js similarity index 99% rename from lib/init/config-rule.js rename to tools/config-rule.js index 131e84a60c5c..91e7eaef5a41 100644 --- a/lib/init/config-rule.js +++ b/tools/config-rule.js @@ -9,7 +9,7 @@ // Requirements //------------------------------------------------------------------------------ -const builtInRules = require("../rules"); +const builtInRules = require("../lib/rules"); //------------------------------------------------------------------------------ // Helpers diff --git a/tools/eslint-fuzzer.js b/tools/eslint-fuzzer.js index 6c5ed9b1bcb4..d951ad3377c1 100644 --- a/tools/eslint-fuzzer.js +++ b/tools/eslint-fuzzer.js @@ -13,7 +13,7 @@ const assert = require("assert"); const eslump = require("eslump"); const espree = require("espree"); const SourceCodeFixer = require("../lib/linter/source-code-fixer"); -const ruleConfigs = require("../lib/init/config-rule").createCoreRuleConfigs(true); +const ruleConfigs = require("./config-rule").createCoreRuleConfigs(true); const sampleMinimizer = require("./code-sample-minimizer"); //------------------------------------------------------------------------------