Skip to content

Commit

Permalink
tools: update to ESLint 4.3.0
Browse files Browse the repository at this point in the history
PR-URL: #14417
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Teddy Katz <teddy.katz@gmail.com>
Reviewed-By: Roman Reiss <me@silverwind.io>
Reviewed-By: Refael Ackermann <refack@gmail.com>
  • Loading branch information
Trott authored and MylesBorins committed Aug 16, 2017
1 parent 431f907 commit 0c0d782
Show file tree
Hide file tree
Showing 12 changed files with 748 additions and 442 deletions.
7 changes: 3 additions & 4 deletions tools/eslint/bin/eslint.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ process.once("uncaughtException", err => {
if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8"));

console.log("\nOops! Something went wrong! :(");
console.log(`\n${template(err.messageData || {})}`);
console.error("\nOops! Something went wrong! :(");
console.error(`\n${template(err.messageData || {})}`);
} else {
console.log(err.message);
console.log(err.stack);
console.error(err.stack);
}

process.exitCode = 1;
Expand Down
137 changes: 123 additions & 14 deletions tools/eslint/lib/config/config-initializer.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
const util = require("util"),
inquirer = require("inquirer"),
ProgressBar = require("progress"),
semver = require("semver"),
autoconfig = require("./autoconfig.js"),
ConfigFile = require("./config-file"),
ConfigOps = require("./config-ops"),
getSourceCodeOfFiles = require("../util/source-code-util").getSourceCodeOfFiles,
ModuleResolver = require("../util/module-resolver"),
npmUtil = require("../util/npm-util"),
recConfig = require("../../conf/eslint-recommended"),
log = require("../logging");
Expand Down Expand Up @@ -56,12 +58,35 @@ function writeFile(config, format) {
}
}

/**
* 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 `npmUtil.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`.
*/
function getPeerDependencies(moduleName) {
let result = getPeerDependencies.cache.get(moduleName);

if (!result) {
log.info(`Checking peerDependencies of ${moduleName}`);

result = npmUtil.fetchPeerDependencies(moduleName);
getPeerDependencies.cache.set(moduleName, result);
}

return result;
}
getPeerDependencies.cache = new Map();

/**
* Synchronously install 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 {void}
*/
function installModules(config) {
function installModules(config, installESLint) {
const modules = {};

// Create a list of modules which should be installed based on config
Expand All @@ -73,11 +98,10 @@ function installModules(config) {
if (config.extends && config.extends.indexOf("eslint:") === -1) {
const moduleName = `eslint-config-${config.extends}`;

log.info(`Checking peerDependencies of ${moduleName}`);
modules[moduleName] = "latest";
Object.assign(
modules,
npmUtil.fetchPeerDependencies(`${moduleName}@latest`)
getPeerDependencies(`${moduleName}@latest`)
);
}

Expand All @@ -86,15 +110,17 @@ function installModules(config) {
return;
}

// Add eslint to list in case user does not have it installed locally
modules.eslint = modules.eslint || "latest";

// Mark to show messages if it's new installation of eslint.
const installStatus = npmUtil.checkDevDeps(["eslint"]);
if (installESLint === false) {
delete modules.eslint;
} else {
const installStatus = npmUtil.checkDevDeps(["eslint"]);

if (installStatus.eslint === false) {
log.info("Local ESLint installation not found.");
config.installedESLint = true;
// 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;
}
}

// Install packages
Expand Down Expand Up @@ -265,9 +291,10 @@ function processAnswers(answers) {
/**
* process user's style guide of choice and return an appropriate config object.
* @param {string} guide name of the chosen style guide
* @param {boolean} [installESLint=true] If `false` is given, it does not install eslint.
* @returns {Object} config object
*/
function getConfigForStyleGuide(guide) {
function getConfigForStyleGuide(guide, installESLint) {
const guides = {
google: { extends: "google" },
airbnb: { extends: "airbnb" },
Expand All @@ -279,11 +306,74 @@ function getConfigForStyleGuide(guide) {
throw new Error("You referenced an unsupported guide.");
}

installModules(guides[guide]);
installModules(guides[guide], installESLint);

return guides[guide];
}

/**
* 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 resolver = new ModuleResolver();
const eslintPath = resolver.resolve("eslint", process.cwd());
const eslint = require(eslintPath);

return eslint.linter.version || null;
} catch (_err) {
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.airbnbReact) {
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 requiredESLintVersionRange = getPeerDependencies(moduleName).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;
}

/* istanbul ignore next: no need to test inquirer*/
/**
* Ask use a few questions on command prompt
Expand Down Expand Up @@ -346,6 +436,21 @@ function promptUser() {
when(answers) {
return ((answers.source === "guide" && answers.packageJsonExists) || answers.source === "auto");
}
},
{
type: "confirm",
name: "installESLint",
message(answers) {
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}?`;
},
default: true,
when(answers) {
return answers.source === "guide" && answers.packageJsonExists && hasESLintVersionConflict(answers);
}
}
]).then(earlyAnswers => {

Expand All @@ -355,11 +460,14 @@ function promptUser() {
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.airbnbReact) {
earlyAnswers.styleguide = "airbnb-base";
}

config = getConfigForStyleGuide(earlyAnswers.styleguide);
config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
writeFile(config, earlyAnswers.format);

return void 0;
Expand Down Expand Up @@ -479,6 +587,7 @@ function promptUser() {

const init = {
getConfigForStyleGuide,
hasESLintVersionConflict,
processAnswers,
/* istanbul ignore next */initializeConfig() {
return promptUser();
Expand Down
12 changes: 12 additions & 0 deletions tools/eslint/lib/config/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,20 @@ class Plugins {
* @param {string[]} pluginNames An array of plugins names.
* @returns {void}
* @throws {Error} If a plugin cannot be loaded.
* @throws {Error} If "plugins" in config is not an array
*/
loadAll(pluginNames) {

// if "plugins" in config is not an array, throw an error so user can fix their config.
if (!Array.isArray(pluginNames)) {
const pluginNotArrayMessage = "ESLint configuration error: \"plugins\" value must be an array";

debug(`${pluginNotArrayMessage}: ${JSON.stringify(pluginNames)}`);

throw new Error(pluginNotArrayMessage);
}

// load each plugin by name
pluginNames.forEach(this.load, this);
}
}
Expand Down
Loading

0 comments on commit 0c0d782

Please sign in to comment.