From 2e6661230bd31a1e75b7c6cb9454e526e355176b Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Sat, 17 Aug 2019 12:31:05 -0400 Subject: [PATCH 01/10] WIP: spike info flag implementation (refs eslint/eslint#11958) --- bin/eslint.js | 35 ++++++++---- lib/info/index.js | 142 ++++++++++++++++++++++++++++++++++++++++++++++ lib/options.js | 6 ++ 3 files changed, 172 insertions(+), 11 deletions(-) create mode 100644 lib/info/index.js diff --git a/bin/eslint.js b/bin/eslint.js index 061e94767f0..7bf35d24bc8 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -16,9 +16,10 @@ require("v8-compile-cache"); // Helpers //------------------------------------------------------------------------------ -const useStdIn = (process.argv.indexOf("--stdin") > -1), - init = (process.argv.indexOf("--init") > -1), - debug = (process.argv.indexOf("--debug") > -1); +const useStdIn = process.argv.includes("--stdin"), + init = process.argv.includes("--init"), + debug = process.argv.includes("--debug"), + info = process.argv.includes("--info"); // must do this initialization *before* other requires in order to work if (debug) { @@ -30,9 +31,10 @@ if (debug) { //------------------------------------------------------------------------------ // now we can safely include the other modules that use debug -const cli = require("../lib/cli"), - path = require("path"), - fs = require("fs"); +const path = require("path"), + fs = require("fs"), + cli = require("../lib/cli"), + logger = require("../lib/shared/logging"); //------------------------------------------------------------------------------ // Execution @@ -47,11 +49,11 @@ process.once("uncaughtException", err => { const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8")); const pkg = require("../package.json"); - console.error("\nOops! Something went wrong! :("); - console.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`); + logger.error("\nOops! Something went wrong! :("); + logger.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`); } else { - console.error(err.stack); + logger.error(err.stack); } process.exitCode = 2; @@ -67,6 +69,17 @@ if (useStdIn) { const STDIN_FILE_DESCRIPTOR = 0; process.exitCode = cli.execute(process.argv, fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")); +} else if (info) { + const infoLogger = require("../lib/info"); + + try { + infoLogger.log(); + process.exitCode = 0; + } catch (err) { + process.exitCode = 1; + logger.error(err.message); + logger.error(err.stack); + } } else if (init) { const configInit = require("../lib/init/config-initializer"); @@ -74,8 +87,8 @@ if (useStdIn) { process.exitCode = 0; }).catch(err => { process.exitCode = 1; - console.error(err.message); - console.error(err.stack); + logger.error(err.message); + logger.error(err.stack); }); } else { process.exitCode = cli.execute(process.argv); diff --git a/lib/info/index.js b/lib/info/index.js new file mode 100644 index 00000000000..dfa27bb138c --- /dev/null +++ b/lib/info/index.js @@ -0,0 +1,142 @@ +/** + * @fileoverview Log information for debugging purposes + * @author Kai Cataldo + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const path = require("path"); +const spawn = require("cross-spawn"); +const { isEmpty } = require("lodash"); +const logger = require("../shared/logging"); + +//------------------------------------------------------------------------------ +// Helpers +//------------------------------------------------------------------------------ + +const STRIP_VERSION_REGEX = /v(\d)/u; + +/** + * Checks if a given path is in a given directory. + * @param {string} parentPath - The parent path to check. + * @param {string} childPath - The path to check. + * @returns {boolean} Whether or not the given path is in the given directory. + */ +function isInDirectory(parentPath, childPath) { + return !path.relative(parentPath, childPath).startsWith(".."); +} + +/** + * Synchronously executes a shell command and formats the result. + * @param {string} cmd - The command to execute. + * @param {Array} args - The arguments to be executed with the command. + * @returns {string} The version returned by the command. + */ +function execCommand(cmd, args) { + const process = spawn.sync(cmd, args, { encoding: "utf8" }); + + if (process.error) { + throw process.error; + } + + return process.stdout.trim(); +} + +/** + * Normalizes a version number. + * @param {string} version - The string to normalize. + * @returns {string} The normalized version number. + */ +function normalizeVersionNumber(version) { + return version.replace(STRIP_VERSION_REGEX, "$1"); +} + +/** + * Gets bin version. + * @param {string} bin - The bin to check. + * @returns {string} The normalized version returned by the command. + */ +function getBinVersion(bin) { + const binArgs = ["--version"]; + + try { + return execCommand(bin, binArgs); + } catch (e) { + logger.error(`Error finding ${bin} version running the command ${bin} ${binArgs.join(" ")}`); + throw e; + } +} + +/** + * Gets installed npm package version. + * @param {string} pkg - The package to check. + * @param {boolean} global - Whether to check globally or not. + * @returns {string} The normalized version returned by the command. + */ +function getNpmPackageVersion(pkg, { global = false } = {}) { + const npmBinArgs = ["bin", "-g"]; + const npmLsArgs = ["ls", "--depth=0", "--json", "eslint"]; + + if (global) { + npmLsArgs.push("-g"); + } + + try { + const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs)); + + if (isEmpty(parsedStdout)) { + return "Not found"; + } + + const [, processBinPath] = process.argv; + let npmBinPath; + + try { + npmBinPath = execCommand("npm", npmBinArgs); + } catch (e) { + logger.error(`Error finding npm binary path when running command npm ${npmBinArgs.join(" ")}`); + throw e; + } + + const isGlobal = isInDirectory(npmBinPath, processBinPath); + let version = parsedStdout.dependencies.eslint.version; + + if ((global && isGlobal) || (!global && !isGlobal)) { + version += " (Currently used)"; + } + + return version; + } catch (e) { + logger.error(`Error finding ${pkg} version running the command npm ${npmLsArgs.join(" ")}`); + throw e; + } +} + +/** + * Generates and returns execution environment information. + * @returns {string} A string that contains execution environment information + */ +function generateInfo() { + return [ + "Environment Info:", + "", + `Node version: ${normalizeVersionNumber(getBinVersion("node"))}`, + `npm version: ${normalizeVersionNumber(getBinVersion("npm"))}`, + `Local ESLint version: ${normalizeVersionNumber(getNpmPackageVersion("eslint", { global: false }))}`, + `Global ESLint version: ${normalizeVersionNumber(getNpmPackageVersion("eslint", { global: true }))}` + ].join("\n"); +} + +//------------------------------------------------------------------------------ +// Public Interface +//------------------------------------------------------------------------------ + +module.exports = { + log() { + logger.info(generateInfo()); + } +}; diff --git a/lib/options.js b/lib/options.js index 440773a844b..2e7576aaac9 100644 --- a/lib/options.js +++ b/lib/options.js @@ -224,6 +224,12 @@ module.exports = optionator({ default: "false", description: "Run config initialization wizard" }, + { + option: "info", + type: "Boolean", + default: "false", + description: "Output environment information for debugging purposes" + }, { option: "debug", type: "Boolean", From c77b7a50e7cd5da6ff16961ad5c9aafd41953076 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Wed, 11 Sep 2019 19:35:28 -0400 Subject: [PATCH 02/10] Move into lib/cli --- bin/eslint.js | 28 +++++++--------------------- lib/cli.js | 15 +++++++++++---- 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/bin/eslint.js b/bin/eslint.js index 7bf35d24bc8..82bcc1e033b 100755 --- a/bin/eslint.js +++ b/bin/eslint.js @@ -18,8 +18,7 @@ require("v8-compile-cache"); const useStdIn = process.argv.includes("--stdin"), init = process.argv.includes("--init"), - debug = process.argv.includes("--debug"), - info = process.argv.includes("--info"); + debug = process.argv.includes("--debug"); // must do this initialization *before* other requires in order to work if (debug) { @@ -33,8 +32,7 @@ if (debug) { // now we can safely include the other modules that use debug const path = require("path"), fs = require("fs"), - cli = require("../lib/cli"), - logger = require("../lib/shared/logging"); + cli = require("../lib/cli"); //------------------------------------------------------------------------------ // Execution @@ -49,11 +47,10 @@ process.once("uncaughtException", err => { const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8")); const pkg = require("../package.json"); - logger.error("\nOops! Something went wrong! :("); - logger.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`); + console.error("\nOops! Something went wrong! :("); + console.error(`\nESLint: ${pkg.version}.\n\n${template(err.messageData || {})}`); } else { - - logger.error(err.stack); + console.error(err.stack); } process.exitCode = 2; @@ -69,17 +66,6 @@ if (useStdIn) { const STDIN_FILE_DESCRIPTOR = 0; process.exitCode = cli.execute(process.argv, fs.readFileSync(STDIN_FILE_DESCRIPTOR, "utf8")); -} else if (info) { - const infoLogger = require("../lib/info"); - - try { - infoLogger.log(); - process.exitCode = 0; - } catch (err) { - process.exitCode = 1; - logger.error(err.message); - logger.error(err.stack); - } } else if (init) { const configInit = require("../lib/init/config-initializer"); @@ -87,8 +73,8 @@ if (useStdIn) { process.exitCode = 0; }).catch(err => { process.exitCode = 1; - logger.error(err.message); - logger.error(err.stack); + console.error(err.message); + console.error(err.stack); }); } else { process.exitCode = cli.execute(process.argv); diff --git a/lib/cli.js b/lib/cli.js index c34545544b1..62d715c3863 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -20,6 +20,7 @@ const fs = require("fs"), mkdirp = require("mkdirp"), { CLIEngine } = require("./cli-engine"), options = require("./options"), + infoLogger = require("./info"), log = require("./shared/logging"); const debug = require("debug")("eslint:cli"); @@ -163,9 +164,16 @@ const cli = { const useStdin = typeof text === "string"; if (currentOptions.version) { // version from package.json - log.info(`v${require("../package.json").version}`); - + } else if (currentOptions.info) { + try { + infoLogger.log(); + return 0; + } catch (err) { + log.error(err.message); + log.error(err.stack); + return 2; + } } else if (currentOptions.printConfig) { if (files.length) { log.error("The --print-config option must be used with exactly one file name."); @@ -227,9 +235,8 @@ const cli = { return (report.errorCount || tooManyWarnings) ? 1 : 0; } - return 2; - + return 2; } return 0; From 9078757ea25ed9bfafde1c23d987c8ba93cb136b Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Wed, 11 Sep 2019 19:40:38 -0400 Subject: [PATCH 03/10] logger -> log for consistency --- lib/info/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/info/index.js b/lib/info/index.js index dfa27bb138c..da6d277652e 100644 --- a/lib/info/index.js +++ b/lib/info/index.js @@ -12,7 +12,7 @@ const path = require("path"); const spawn = require("cross-spawn"); const { isEmpty } = require("lodash"); -const logger = require("../shared/logging"); +const log = require("../shared/logging"); //------------------------------------------------------------------------------ // Helpers @@ -66,7 +66,7 @@ function getBinVersion(bin) { try { return execCommand(bin, binArgs); } catch (e) { - logger.error(`Error finding ${bin} version running the command ${bin} ${binArgs.join(" ")}`); + log.error(`Error finding ${bin} version running the command ${bin} ${binArgs.join(" ")}`); throw e; } } @@ -98,7 +98,7 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { try { npmBinPath = execCommand("npm", npmBinArgs); } catch (e) { - logger.error(`Error finding npm binary path when running command npm ${npmBinArgs.join(" ")}`); + log.error(`Error finding npm binary path when running command npm ${npmBinArgs.join(" ")}`); throw e; } @@ -111,7 +111,7 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { return version; } catch (e) { - logger.error(`Error finding ${pkg} version running the command npm ${npmLsArgs.join(" ")}`); + log.error(`Error finding ${pkg} version running the command npm ${npmLsArgs.join(" ")}`); throw e; } } @@ -137,6 +137,6 @@ function generateInfo() { module.exports = { log() { - logger.info(generateInfo()); + log.info(generateInfo()); } }; From 6e46467101ffbe3f185d0cd5c99c769839d33097 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Wed, 11 Sep 2019 20:06:49 -0400 Subject: [PATCH 04/10] Colocate generation of logged info --- lib/cli.js | 8 ++++---- lib/info/index.js | 39 ++++++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/lib/cli.js b/lib/cli.js index 62d715c3863..2b7e8bd9dd5 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -20,7 +20,7 @@ const fs = require("fs"), mkdirp = require("mkdirp"), { CLIEngine } = require("./cli-engine"), options = require("./options"), - infoLogger = require("./info"), + info = require("./info"), log = require("./shared/logging"); const debug = require("debug")("eslint:cli"); @@ -163,11 +163,11 @@ const cli = { const useStdin = typeof text === "string"; - if (currentOptions.version) { // version from package.json - log.info(`v${require("../package.json").version}`); + if (currentOptions.version) { + log.info(info.version()); } else if (currentOptions.info) { try { - infoLogger.log(); + log.info(info.environment()); return 0; } catch (err) { log.error(err.message); diff --git a/lib/info/index.js b/lib/info/index.js index da6d277652e..a08345c8037 100644 --- a/lib/info/index.js +++ b/lib/info/index.js @@ -18,8 +18,6 @@ const log = require("../shared/logging"); // Helpers //------------------------------------------------------------------------------ -const STRIP_VERSION_REGEX = /v(\d)/u; - /** * Checks if a given path is in a given directory. * @param {string} parentPath - The parent path to check. @@ -48,11 +46,11 @@ function execCommand(cmd, args) { /** * Normalizes a version number. - * @param {string} version - The string to normalize. + * @param {string} versionStr - The string to normalize. * @returns {string} The normalized version number. */ -function normalizeVersionNumber(version) { - return version.replace(STRIP_VERSION_REGEX, "$1"); +function normalizeVersionStr(versionStr) { + return versionStr.startsWith("v") ? versionStr : `v${versionStr}`; } /** @@ -103,13 +101,13 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { } const isGlobal = isInDirectory(npmBinPath, processBinPath); - let version = parsedStdout.dependencies.eslint.version; + let pkgVersion = parsedStdout.dependencies.eslint.version; if ((global && isGlobal) || (!global && !isGlobal)) { - version += " (Currently used)"; + pkgVersion += " (Currently used)"; } - return version; + return pkgVersion; } catch (e) { log.error(`Error finding ${pkg} version running the command npm ${npmLsArgs.join(" ")}`); throw e; @@ -118,25 +116,32 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { /** * Generates and returns execution environment information. - * @returns {string} A string that contains execution environment information + * @returns {string} A string that contains execution environment information. */ -function generateInfo() { +function environment() { return [ "Environment Info:", "", - `Node version: ${normalizeVersionNumber(getBinVersion("node"))}`, - `npm version: ${normalizeVersionNumber(getBinVersion("npm"))}`, - `Local ESLint version: ${normalizeVersionNumber(getNpmPackageVersion("eslint", { global: false }))}`, - `Global ESLint version: ${normalizeVersionNumber(getNpmPackageVersion("eslint", { global: true }))}` + `Node version: ${normalizeVersionStr(getBinVersion("node"))}`, + `npm version: ${normalizeVersionStr(getBinVersion("npm"))}`, + `Local ESLint version: ${normalizeVersionStr(getNpmPackageVersion("eslint", { global: false }))}`, + `Global ESLint version: ${normalizeVersionStr(getNpmPackageVersion("eslint", { global: true }))}` ].join("\n"); } +/** + * Returns version of currently executing ESLint. + * @returns {string} The version from the currently executing ESLint's package.json. + */ +function version() { + return `v${require("../../package.json").version}`; +} + //------------------------------------------------------------------------------ // Public Interface //------------------------------------------------------------------------------ module.exports = { - log() { - log.info(generateInfo()); - } + environment, + version }; From 4ec6e11dfbaf6348e651ab8bd131f9488d8fdb0e Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Wed, 11 Sep 2019 20:13:23 -0400 Subject: [PATCH 05/10] Make error logging consistent --- lib/cli.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/cli.js b/lib/cli.js index 2b7e8bd9dd5..292d7dd4dbc 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -171,7 +171,6 @@ const cli = { return 0; } catch (err) { log.error(err.message); - log.error(err.stack); return 2; } } else if (currentOptions.printConfig) { From 8ae35439ec763b13c4273942afa4db8269a35306 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Thu, 12 Sep 2019 18:38:30 -0400 Subject: [PATCH 06/10] Add tests --- lib/cli.js | 9 +- lib/{info/index.js => shared/info.js} | 37 +++-- tests/lib/cli.js | 27 +++- tests/lib/shared/info.js | 211 ++++++++++++++++++++++++++ 4 files changed, 253 insertions(+), 31 deletions(-) rename lib/{info/index.js => shared/info.js} (74%) create mode 100644 tests/lib/shared/info.js diff --git a/lib/cli.js b/lib/cli.js index 292d7dd4dbc..c1b0d2223c5 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -20,8 +20,8 @@ const fs = require("fs"), mkdirp = require("mkdirp"), { CLIEngine } = require("./cli-engine"), options = require("./options"), - info = require("./info"), - log = require("./shared/logging"); + log = require("./shared/logging"), + info = require("./shared/info"); const debug = require("debug")("eslint:cli"); @@ -160,7 +160,6 @@ const cli = { } const files = currentOptions._; - const useStdin = typeof text === "string"; if (currentOptions.version) { @@ -184,17 +183,13 @@ const cli = { } const engine = new CLIEngine(translateOptions(currentOptions)); - const fileConfig = engine.getConfigForFile(currentOptions.printConfig); log.info(JSON.stringify(fileConfig, null, " ")); return 0; } else if (currentOptions.help || (!files.length && !useStdin)) { - log.info(options.generateHelp()); - } else { - debug(`Running on ${useStdin ? "text" : "files"}`); if (currentOptions.fix && currentOptions.fixDryRun) { diff --git a/lib/info/index.js b/lib/shared/info.js similarity index 74% rename from lib/info/index.js rename to lib/shared/info.js index a08345c8037..324f457d80b 100644 --- a/lib/info/index.js +++ b/lib/shared/info.js @@ -1,5 +1,5 @@ /** - * @fileoverview Log information for debugging purposes + * @fileoverview Utility to get information about the execution environment. * @author Kai Cataldo */ @@ -13,18 +13,19 @@ const path = require("path"); const spawn = require("cross-spawn"); const { isEmpty } = require("lodash"); const log = require("../shared/logging"); +const packageJson = require("../../package.json"); //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** - * Checks if a given path is in a given directory. + * Checks if a path is a child of a directory. * @param {string} parentPath - The parent path to check. * @param {string} childPath - The path to check. - * @returns {boolean} Whether or not the given path is in the given directory. + * @returns {boolean} Whether or not the given path is a child of a directory. */ -function isInDirectory(parentPath, childPath) { +function isChildOfDirectory(parentPath, childPath) { return !path.relative(parentPath, childPath).startsWith(".."); } @@ -62,9 +63,9 @@ function getBinVersion(bin) { const binArgs = ["--version"]; try { - return execCommand(bin, binArgs); + return normalizeVersionStr(execCommand(bin, binArgs)); } catch (e) { - log.error(`Error finding ${bin} version running the command ${bin} ${binArgs.join(" ")}`); + log.error(`Error finding ${bin} version running the command \`${bin} ${binArgs.join(" ")}\``); throw e; } } @@ -86,7 +87,11 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { try { const parsedStdout = JSON.parse(execCommand("npm", npmLsArgs)); - if (isEmpty(parsedStdout)) { + /* + * Checking globally returns an empty JSON object, while local checks + * include the name and version of the local project. + */ + if (isEmpty(parsedStdout) || !(parsedStdout.dependencies && parsedStdout.dependencies.eslint)) { return "Not found"; } @@ -96,20 +101,20 @@ function getNpmPackageVersion(pkg, { global = false } = {}) { try { npmBinPath = execCommand("npm", npmBinArgs); } catch (e) { - log.error(`Error finding npm binary path when running command npm ${npmBinArgs.join(" ")}`); + log.error(`Error finding npm binary path when running command \`npm ${npmBinArgs.join(" ")}\``); throw e; } - const isGlobal = isInDirectory(npmBinPath, processBinPath); + const isGlobal = isChildOfDirectory(npmBinPath, processBinPath); let pkgVersion = parsedStdout.dependencies.eslint.version; if ((global && isGlobal) || (!global && !isGlobal)) { pkgVersion += " (Currently used)"; } - return pkgVersion; + return normalizeVersionStr(pkgVersion); } catch (e) { - log.error(`Error finding ${pkg} version running the command npm ${npmLsArgs.join(" ")}`); + log.error(`Error finding ${pkg} version running the command \`npm ${npmLsArgs.join(" ")}\``); throw e; } } @@ -122,10 +127,10 @@ function environment() { return [ "Environment Info:", "", - `Node version: ${normalizeVersionStr(getBinVersion("node"))}`, - `npm version: ${normalizeVersionStr(getBinVersion("npm"))}`, - `Local ESLint version: ${normalizeVersionStr(getNpmPackageVersion("eslint", { global: false }))}`, - `Global ESLint version: ${normalizeVersionStr(getNpmPackageVersion("eslint", { global: true }))}` + `Node version: ${getBinVersion("node")}`, + `npm version: ${getBinVersion("npm")}`, + `Local ESLint version: ${getNpmPackageVersion("eslint", { global: false })}`, + `Global ESLint version: ${getNpmPackageVersion("eslint", { global: true })}` ].join("\n"); } @@ -134,7 +139,7 @@ function environment() { * @returns {string} The version from the currently executing ESLint's package.json. */ function version() { - return `v${require("../../package.json").version}`; + return `v${packageJson.version}`; } //------------------------------------------------------------------------------ diff --git a/tests/lib/cli.js b/tests/lib/cli.js index baae75b53cb..c555b448bc1 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -29,14 +29,18 @@ const proxyquire = require("proxyquire").noCallThru().noPreserveCache(); //------------------------------------------------------------------------------ describe("cli", () => { - let fixtureDir; const log = { info: sinon.spy(), error: sinon.spy() }; + const info = { + environment: sinon.stub(), + version: sinon.stub() + }; const cli = proxyquire("../../lib/cli", { - "./shared/logging": log + "./shared/logging": log, + "./shared/info": info }); /** @@ -324,15 +328,27 @@ describe("cli", () => { describe("when executing with version flag", () => { it("should print out current version", () => { assert.strictEqual(cli.execute("-v"), 0); + assert.strictEqual(log.info.callCount, 1); + }); + }); + describe("when executing with info flag", () => { + it("should print out environment information", () => { + assert.strictEqual(cli.execute("--info"), 0); assert.strictEqual(log.info.callCount, 1); }); + + it("should print error message and return error code", () => { + info.environment.throws("There was an error!"); + + assert.strictEqual(cli.execute("--info"), 2); + assert.strictEqual(log.error.callCount, 1); + }); }); describe("when executing with help flag", () => { it("should print out help", () => { assert.strictEqual(cli.execute("-h"), 0); - assert.strictEqual(log.info.callCount, 1); }); }); @@ -349,7 +365,6 @@ describe("cli", () => { }); describe("when given a file in excluded files list", () => { - it("should not process the file", () => { const ignorePath = getFixturePath(".eslintignore"); const filePath = getFixturePath("passing.js"); @@ -398,7 +413,6 @@ describe("cli", () => { }); describe("when executing a file with a shebang", () => { - it("should execute without error", () => { const filePath = getFixturePath("shebang.js"); const exit = cli.execute(`--no-ignore ${filePath}`); @@ -408,7 +422,6 @@ describe("cli", () => { }); describe("when loading a custom rule", () => { - it("should return an error when rule isn't found", () => { const rulesPath = getFixturePath("rules", "wrong"); const configPath = getFixturePath("rules", "eslint.json"); @@ -551,7 +564,6 @@ describe("cli", () => { }); describe("when supplied with report output file path", () => { - afterEach(() => { sh.rm("-rf", "tests/output"); }); @@ -594,7 +606,6 @@ describe("cli", () => { }); describe("when supplied with a plugin", () => { - it("should pass plugins to CLIEngine", () => { const examplePluginName = "eslint-plugin-example"; diff --git a/tests/lib/shared/info.js b/tests/lib/shared/info.js new file mode 100644 index 00000000000..ca7b8bbe230 --- /dev/null +++ b/tests/lib/shared/info.js @@ -0,0 +1,211 @@ +/** + * @fileoverview Tests for info util + * @author Kai Cataldo + */ + +"use strict"; + +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const assert = require("chai").assert; +const sinon = require("sinon"); +const spawn = require("cross-spawn"); +const { unIndent } = require("../_utils"); +const info = require("../../../lib/shared/info"); +const log = require("../../../lib/shared/logging"); +const packageJson = require("../../../package.json"); + +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const LOCAL_ESLINT_BIN_PATH = "/Users/username/code/project/node_modules/eslint/bin/eslint.js"; +const GLOBAL_ESLINT_BIN_PATH = "/usr/local/bin/npm/node_modules/eslint/bin/eslint.js"; + +/** + * Sets up spawn.sync() stub calls to return values and throw errors in the order in which they are given. + * @param {Function} stub - The stub to set up. + * @param {Array} returnVals - Values to be returned by subsequent stub calls. + * @returns {Function} The set up stub. + */ +function setupSpawnSyncStubReturnVals(stub, returnVals) { + let stubChain = stub; + + for (const [i, val] of returnVals.entries()) { + const returnVal = val instanceof Error + ? { error: val } + : { stdout: val }; + + stubChain = stubChain.onCall(i).returns(returnVal); + } + + return stubChain; +} + +describe("info", () => { + describe("environment()", () => { + let spawnSyncStub; + let logErrorStub; + let originalProcessArgv; + let spawnSyncStubArgs; + + beforeEach(() => { + spawnSyncStub = sinon.stub(spawn, "sync"); + logErrorStub = sinon.stub(log, "error"); + originalProcessArgv = process.argv; + process.argv[1] = LOCAL_ESLINT_BIN_PATH; + spawnSyncStubArgs = [ + "v12.8.0", + "6.11.3", + unIndent` + { + "name": "project", + "version": "1.0.0", + "dependencies": { + "eslint": { + "version": "6.3.0" + } + } + } + `, + "/usr/local/bin/npm", + unIndent` + { + "dependencies": { + "eslint": { + "version": "5.16.0", + "from": "eslint", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.16.0.tgz" + } + } + } + `, + "/usr/local/bin/npm" + ]; + }); + + afterEach(() => { + spawnSyncStub.restore(); + logErrorStub.restore(); + process.argv = originalProcessArgv; + }); + + + it("should return a string containing environment information when running local installation", () => { + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: v6.3.0 (Currently used) + Global ESLint version: v5.16.0 + ` + ); + }); + + it("should return a string containing environment information when running global installation", () => { + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + process.argv[1] = GLOBAL_ESLINT_BIN_PATH; + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: v6.3.0 + Global ESLint version: v5.16.0 (Currently used) + ` + ); + }); + + it("should return a string containing environment information when not installed locally", () => { + spawnSyncStubArgs.splice(2, 2, unIndent` + { + "name": "project", + "version": "1.0.0" + } + `); + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + process.argv[1] = GLOBAL_ESLINT_BIN_PATH; + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: Not found + Global ESLint version: v5.16.0 (Currently used) + ` + ); + }); + + it("should return a string containing environment information when not installed globally", () => { + spawnSyncStubArgs[4] = "{}"; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.strictEqual( + info.environment(), + unIndent` + Environment Info: + + Node version: v12.8.0 + npm version: v6.11.3 + Local ESLint version: v6.3.0 (Currently used) + Global ESLint version: Not found + ` + ); + }); + + it("log and throw an error when npm version can not be found", () => { + const expectedErr = new Error("npm can not be found"); + + spawnSyncStubArgs[1] = expectedErr; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, expectedErr); + assert.strictEqual(logErrorStub.args[0][0], "Error finding npm version running the command `npm --version`"); + }); + + it("log and throw an error when npm binary path can not be found", () => { + const expectedErr = new Error("npm can not be found"); + + spawnSyncStubArgs[3] = expectedErr; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, expectedErr); + assert.strictEqual(logErrorStub.args[0][0], "Error finding npm binary path when running command `npm bin -g`"); + }); + + it("log and throw an error when checking for local ESLint version when returned output of command is malformed", () => { + spawnSyncStubArgs[2] = "This is not JSON"; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, "Unexpected token T in JSON at position 0"); + assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint`"); + }); + + it("log and throw an error when checking for global ESLint version when returned output of command is malformed", () => { + spawnSyncStubArgs[4] = "This is not JSON"; + setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); + + assert.throws(info.environment, "Unexpected token T in JSON at position 0"); + assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint -g`"); + }); + }); + + describe("version()", () => { + it("should return the version of the package defined in package.json", () => { + assert.strictEqual(info.version(), `v${packageJson.version}`); + }); + }); +}); From a84a3a40ab715bf8662e7040d1a14cae08fa3eb5 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Mon, 16 Sep 2019 02:28:24 -0400 Subject: [PATCH 07/10] Update documentation --- docs/user-guide/command-line-interface.md | 5 +++++ lib/options.js | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md index 532d8f94735..64a85e25420 100644 --- a/docs/user-guide/command-line-interface.md +++ b/docs/user-guide/command-line-interface.md @@ -78,6 +78,7 @@ Caching: Miscellaneous: --init Run config initialization wizard - default: false + --info Output execution environment information - default: false --debug Output debugging information -h, --help Show help -v, --version Output the version number @@ -446,6 +447,10 @@ This option will start config initialization wizard. It's designed to help new u The resulting configuration file will be created in the current directory. +#### `--info` + +This option outputs information about the execution environment, including the version of Node, npm, and local and global installations of ESLint. The ESLint team may ask for this information to help solve bugs. + #### `--debug` This option outputs debugging information to the console. This information is useful when you're seeing a problem and having a hard time pinpointing it. The ESLint team may ask for this debugging information to help solve bugs. diff --git a/lib/options.js b/lib/options.js index 2e7576aaac9..227093e4b13 100644 --- a/lib/options.js +++ b/lib/options.js @@ -228,7 +228,7 @@ module.exports = optionator({ option: "info", type: "Boolean", default: "false", - description: "Output environment information for debugging purposes" + description: "Output execution environment information" }, { option: "debug", From 86c4f5ba7379f66d5fc031829b6dfd131fe05b1a Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Mon, 16 Sep 2019 19:59:15 -0400 Subject: [PATCH 08/10] Rename lib/shared/info.js -> lib/shared/runtime-info.js --- lib/cli.js | 6 ++--- lib/shared/{info.js => runtime-info.js} | 0 tests/lib/cli.js | 6 ++--- tests/lib/shared/{info.js => runtime-info.js} | 24 +++++++++---------- 4 files changed, 18 insertions(+), 18 deletions(-) rename lib/shared/{info.js => runtime-info.js} (100%) rename tests/lib/shared/{info.js => runtime-info.js} (91%) diff --git a/lib/cli.js b/lib/cli.js index c1b0d2223c5..255ff1b2ace 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -21,7 +21,7 @@ const fs = require("fs"), { CLIEngine } = require("./cli-engine"), options = require("./options"), log = require("./shared/logging"), - info = require("./shared/info"); + RuntimeInfo = require("./shared/runtime-info"); const debug = require("debug")("eslint:cli"); @@ -163,10 +163,10 @@ const cli = { const useStdin = typeof text === "string"; if (currentOptions.version) { - log.info(info.version()); + log.info(RuntimeInfo.version()); } else if (currentOptions.info) { try { - log.info(info.environment()); + log.info(RuntimeInfo.environment()); return 0; } catch (err) { log.error(err.message); diff --git a/lib/shared/info.js b/lib/shared/runtime-info.js similarity index 100% rename from lib/shared/info.js rename to lib/shared/runtime-info.js diff --git a/tests/lib/cli.js b/tests/lib/cli.js index c555b448bc1..859dab5d994 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -34,13 +34,13 @@ describe("cli", () => { info: sinon.spy(), error: sinon.spy() }; - const info = { + const RuntimeInfo = { environment: sinon.stub(), version: sinon.stub() }; const cli = proxyquire("../../lib/cli", { "./shared/logging": log, - "./shared/info": info + "./shared/runtime-info": RuntimeInfo }); /** @@ -339,7 +339,7 @@ describe("cli", () => { }); it("should print error message and return error code", () => { - info.environment.throws("There was an error!"); + RuntimeInfo.environment.throws("There was an error!"); assert.strictEqual(cli.execute("--info"), 2); assert.strictEqual(log.error.callCount, 1); diff --git a/tests/lib/shared/info.js b/tests/lib/shared/runtime-info.js similarity index 91% rename from tests/lib/shared/info.js rename to tests/lib/shared/runtime-info.js index ca7b8bbe230..79ed4ff1ae2 100644 --- a/tests/lib/shared/info.js +++ b/tests/lib/shared/runtime-info.js @@ -1,5 +1,5 @@ /** - * @fileoverview Tests for info util + * @fileoverview Tests for RuntimeInfo util * @author Kai Cataldo */ @@ -13,7 +13,7 @@ const assert = require("chai").assert; const sinon = require("sinon"); const spawn = require("cross-spawn"); const { unIndent } = require("../_utils"); -const info = require("../../../lib/shared/info"); +const RuntimeInfo = require("../../../lib/shared/runtime-info"); const log = require("../../../lib/shared/logging"); const packageJson = require("../../../package.json"); @@ -44,7 +44,7 @@ function setupSpawnSyncStubReturnVals(stub, returnVals) { return stubChain; } -describe("info", () => { +describe("RuntimeInfo", () => { describe("environment()", () => { let spawnSyncStub; let logErrorStub; @@ -97,7 +97,7 @@ describe("info", () => { setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); assert.strictEqual( - info.environment(), + RuntimeInfo.environment(), unIndent` Environment Info: @@ -114,7 +114,7 @@ describe("info", () => { process.argv[1] = GLOBAL_ESLINT_BIN_PATH; assert.strictEqual( - info.environment(), + RuntimeInfo.environment(), unIndent` Environment Info: @@ -137,7 +137,7 @@ describe("info", () => { process.argv[1] = GLOBAL_ESLINT_BIN_PATH; assert.strictEqual( - info.environment(), + RuntimeInfo.environment(), unIndent` Environment Info: @@ -154,7 +154,7 @@ describe("info", () => { setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); assert.strictEqual( - info.environment(), + RuntimeInfo.environment(), unIndent` Environment Info: @@ -172,7 +172,7 @@ describe("info", () => { spawnSyncStubArgs[1] = expectedErr; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(info.environment, expectedErr); + assert.throws(RuntimeInfo.environment, expectedErr); assert.strictEqual(logErrorStub.args[0][0], "Error finding npm version running the command `npm --version`"); }); @@ -182,7 +182,7 @@ describe("info", () => { spawnSyncStubArgs[3] = expectedErr; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(info.environment, expectedErr); + assert.throws(RuntimeInfo.environment, expectedErr); assert.strictEqual(logErrorStub.args[0][0], "Error finding npm binary path when running command `npm bin -g`"); }); @@ -190,7 +190,7 @@ describe("info", () => { spawnSyncStubArgs[2] = "This is not JSON"; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(info.environment, "Unexpected token T in JSON at position 0"); + assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0"); assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint`"); }); @@ -198,14 +198,14 @@ describe("info", () => { spawnSyncStubArgs[4] = "This is not JSON"; setupSpawnSyncStubReturnVals(spawnSyncStub, spawnSyncStubArgs); - assert.throws(info.environment, "Unexpected token T in JSON at position 0"); + assert.throws(RuntimeInfo.environment, "Unexpected token T in JSON at position 0"); assert.strictEqual(logErrorStub.args[0][0], "Error finding eslint version running the command `npm ls --depth=0 --json eslint -g`"); }); }); describe("version()", () => { it("should return the version of the package defined in package.json", () => { - assert.strictEqual(info.version(), `v${packageJson.version}`); + assert.strictEqual(RuntimeInfo.version(), `v${packageJson.version}`); }); }); }); From 21edd785b725aedde62e843bf2748fb8c10eb5ac Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Tue, 17 Sep 2019 00:17:35 -0400 Subject: [PATCH 09/10] Rename --info -> --env-info --- docs/user-guide/command-line-interface.md | 4 ++-- lib/cli.js | 2 +- lib/options.js | 2 +- tests/lib/cli.js | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md index 64a85e25420..8938bc91f24 100644 --- a/docs/user-guide/command-line-interface.md +++ b/docs/user-guide/command-line-interface.md @@ -78,7 +78,7 @@ Caching: Miscellaneous: --init Run config initialization wizard - default: false - --info Output execution environment information - default: false + --env-info Output execution environment information - default: false --debug Output debugging information -h, --help Show help -v, --version Output the version number @@ -447,7 +447,7 @@ This option will start config initialization wizard. It's designed to help new u The resulting configuration file will be created in the current directory. -#### `--info` +#### `--env-info` This option outputs information about the execution environment, including the version of Node, npm, and local and global installations of ESLint. The ESLint team may ask for this information to help solve bugs. diff --git a/lib/cli.js b/lib/cli.js index 255ff1b2ace..18a917cf0b0 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -164,7 +164,7 @@ const cli = { if (currentOptions.version) { log.info(RuntimeInfo.version()); - } else if (currentOptions.info) { + } else if (currentOptions.envInfo) { try { log.info(RuntimeInfo.environment()); return 0; diff --git a/lib/options.js b/lib/options.js index 227093e4b13..83bf9afc22c 100644 --- a/lib/options.js +++ b/lib/options.js @@ -225,7 +225,7 @@ module.exports = optionator({ description: "Run config initialization wizard" }, { - option: "info", + option: "env-info", type: "Boolean", default: "false", description: "Output execution environment information" diff --git a/tests/lib/cli.js b/tests/lib/cli.js index 859dab5d994..1b98fb137a7 100644 --- a/tests/lib/cli.js +++ b/tests/lib/cli.js @@ -332,16 +332,16 @@ describe("cli", () => { }); }); - describe("when executing with info flag", () => { + describe("when executing with env-info flag", () => { it("should print out environment information", () => { - assert.strictEqual(cli.execute("--info"), 0); + assert.strictEqual(cli.execute("--env-info"), 0); assert.strictEqual(log.info.callCount, 1); }); it("should print error message and return error code", () => { RuntimeInfo.environment.throws("There was an error!"); - assert.strictEqual(cli.execute("--info"), 2); + assert.strictEqual(cli.execute("--env-info"), 2); assert.strictEqual(log.error.callCount, 1); }); }); From 831b12e81419e61a34e2b202f75852e73e3a26a2 Mon Sep 17 00:00:00 2001 From: Kai Cataldo Date: Tue, 17 Sep 2019 00:36:59 -0400 Subject: [PATCH 10/10] Refactor test file --- tests/lib/shared/runtime-info.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/lib/shared/runtime-info.js b/tests/lib/shared/runtime-info.js index 79ed4ff1ae2..77ada8bc78d 100644 --- a/tests/lib/shared/runtime-info.js +++ b/tests/lib/shared/runtime-info.js @@ -1,5 +1,5 @@ /** - * @fileoverview Tests for RuntimeInfo util + * @fileoverview Tests for RuntimeInfo util. * @author Kai Cataldo */ @@ -18,12 +18,9 @@ const log = require("../../../lib/shared/logging"); const packageJson = require("../../../package.json"); //------------------------------------------------------------------------------ -// Tests +// Helpers //------------------------------------------------------------------------------ -const LOCAL_ESLINT_BIN_PATH = "/Users/username/code/project/node_modules/eslint/bin/eslint.js"; -const GLOBAL_ESLINT_BIN_PATH = "/usr/local/bin/npm/node_modules/eslint/bin/eslint.js"; - /** * Sets up spawn.sync() stub calls to return values and throw errors in the order in which they are given. * @param {Function} stub - The stub to set up. @@ -44,6 +41,14 @@ function setupSpawnSyncStubReturnVals(stub, returnVals) { return stubChain; } +//------------------------------------------------------------------------------ +// Tests +//------------------------------------------------------------------------------ + +const LOCAL_ESLINT_BIN_PATH = "/Users/username/code/project/node_modules/eslint/bin/eslint.js"; +const GLOBAL_ESLINT_BIN_PATH = "/usr/local/bin/npm/node_modules/eslint/bin/eslint.js"; +const NPM_BIN_PATH = "/usr/local/bin/npm"; + describe("RuntimeInfo", () => { describe("environment()", () => { let spawnSyncStub; @@ -70,7 +75,7 @@ describe("RuntimeInfo", () => { } } `, - "/usr/local/bin/npm", + NPM_BIN_PATH, unIndent` { "dependencies": { @@ -82,7 +87,7 @@ describe("RuntimeInfo", () => { } } `, - "/usr/local/bin/npm" + NPM_BIN_PATH ]; });