1212const util = require ( "util" ) ,
1313 inquirer = require ( "inquirer" ) ,
1414 ProgressBar = require ( "progress" ) ,
15+ semver = require ( "semver" ) ,
1516 autoconfig = require ( "./autoconfig.js" ) ,
1617 ConfigFile = require ( "./config-file" ) ,
1718 ConfigOps = require ( "./config-ops" ) ,
1819 getSourceCodeOfFiles = require ( "../util/source-code-util" ) . getSourceCodeOfFiles ,
20+ ModuleResolver = require ( "../util/module-resolver" ) ,
1921 npmUtil = require ( "../util/npm-util" ) ,
2022 recConfig = require ( "../../conf/eslint-recommended" ) ,
2123 log = require ( "../logging" ) ;
@@ -56,12 +58,35 @@ function writeFile(config, format) {
5658 }
5759}
5860
61+ /**
62+ * Get the peer dependencies of the given module.
63+ * This adds the gotten value to cache at the first time, then reuses it.
64+ * In a process, this function is called twice, but `npmUtil.fetchPeerDependencies` needs to access network which is relatively slow.
65+ * @param {string } moduleName The module name to get.
66+ * @returns {Object } The peer dependencies of the given module.
67+ * This object is the object of `peerDependencies` field of `package.json`.
68+ */
69+ function getPeerDependencies ( moduleName ) {
70+ let result = getPeerDependencies . cache . get ( moduleName ) ;
71+
72+ if ( ! result ) {
73+ log . info ( `Checking peerDependencies of ${ moduleName } ` ) ;
74+
75+ result = npmUtil . fetchPeerDependencies ( moduleName ) ;
76+ getPeerDependencies . cache . set ( moduleName , result ) ;
77+ }
78+
79+ return result ;
80+ }
81+ getPeerDependencies . cache = new Map ( ) ;
82+
5983/**
6084 * Synchronously install necessary plugins, configs, parsers, etc. based on the config
6185 * @param {Object } config config object
86+ * @param {boolean } [installESLint=true] If `false` is given, it does not install eslint.
6287 * @returns {void }
6388 */
64- function installModules ( config ) {
89+ function installModules ( config , installESLint ) {
6590 const modules = { } ;
6691
6792 // Create a list of modules which should be installed based on config
@@ -73,11 +98,10 @@ function installModules(config) {
7398 if ( config . extends && config . extends . indexOf ( "eslint:" ) === - 1 ) {
7499 const moduleName = `eslint-config-${ config . extends } ` ;
75100
76- log . info ( `Checking peerDependencies of ${ moduleName } ` ) ;
77101 modules [ moduleName ] = "latest" ;
78102 Object . assign (
79103 modules ,
80- npmUtil . fetchPeerDependencies ( `${ moduleName } @latest` )
104+ getPeerDependencies ( `${ moduleName } @latest` )
81105 ) ;
82106 }
83107
@@ -86,15 +110,17 @@ function installModules(config) {
86110 return ;
87111 }
88112
89- // Add eslint to list in case user does not have it installed locally
90- modules . eslint = modules . eslint || "latest" ;
91-
92- // Mark to show messages if it's new installation of eslint.
93- const installStatus = npmUtil . checkDevDeps ( [ "eslint" ] ) ;
113+ if ( installESLint === false ) {
114+ delete modules . eslint ;
115+ } else {
116+ const installStatus = npmUtil . checkDevDeps ( [ "eslint" ] ) ;
94117
95- if ( installStatus . eslint === false ) {
96- log . info ( "Local ESLint installation not found." ) ;
97- config . installedESLint = true ;
118+ // Mark to show messages if it's new installation of eslint.
119+ if ( installStatus . eslint === false ) {
120+ log . info ( "Local ESLint installation not found." ) ;
121+ modules . eslint = modules . eslint || "latest" ;
122+ config . installedESLint = true ;
123+ }
98124 }
99125
100126 // Install packages
@@ -265,9 +291,10 @@ function processAnswers(answers) {
265291/**
266292 * process user's style guide of choice and return an appropriate config object.
267293 * @param {string } guide name of the chosen style guide
294+ * @param {boolean } [installESLint=true] If `false` is given, it does not install eslint.
268295 * @returns {Object } config object
269296 */
270- function getConfigForStyleGuide ( guide ) {
297+ function getConfigForStyleGuide ( guide , installESLint ) {
271298 const guides = {
272299 google : { extends : "google" } ,
273300 airbnb : { extends : "airbnb" } ,
@@ -279,11 +306,74 @@ function getConfigForStyleGuide(guide) {
279306 throw new Error ( "You referenced an unsupported guide." ) ;
280307 }
281308
282- installModules ( guides [ guide ] ) ;
309+ installModules ( guides [ guide ] , installESLint ) ;
283310
284311 return guides [ guide ] ;
285312}
286313
314+ /**
315+ * Get the version of the local ESLint.
316+ * @returns {string|null } The version. If the local ESLint was not found, returns null.
317+ */
318+ function getLocalESLintVersion ( ) {
319+ try {
320+ const resolver = new ModuleResolver ( ) ;
321+ const eslintPath = resolver . resolve ( "eslint" , process . cwd ( ) ) ;
322+ const eslint = require ( eslintPath ) ;
323+
324+ return eslint . linter . version || null ;
325+ } catch ( _err ) {
326+ return null ;
327+ }
328+ }
329+
330+ /**
331+ * Get the shareable config name of the chosen style guide.
332+ * @param {Object } answers The answers object.
333+ * @returns {string } The shareable config name.
334+ */
335+ function getStyleGuideName ( answers ) {
336+ if ( answers . styleguide === "airbnb" && ! answers . airbnbReact ) {
337+ return "airbnb-base" ;
338+ }
339+ return answers . styleguide ;
340+ }
341+
342+ /**
343+ * Check whether the local ESLint version conflicts with the required version of the chosen shareable config.
344+ * @param {Object } answers The answers object.
345+ * @returns {boolean } `true` if the local ESLint is found then it conflicts with the required version of the chosen shareable config.
346+ */
347+ function hasESLintVersionConflict ( answers ) {
348+
349+ // Get the local ESLint version.
350+ const localESLintVersion = getLocalESLintVersion ( ) ;
351+
352+ if ( ! localESLintVersion ) {
353+ return false ;
354+ }
355+
356+ // Get the required range of ESLint version.
357+ const configName = getStyleGuideName ( answers ) ;
358+ const moduleName = `eslint-config-${ configName } @latest` ;
359+ const requiredESLintVersionRange = getPeerDependencies ( moduleName ) . eslint ;
360+
361+ if ( ! requiredESLintVersionRange ) {
362+ return false ;
363+ }
364+
365+ answers . localESLintVersion = localESLintVersion ;
366+ answers . requiredESLintVersionRange = requiredESLintVersionRange ;
367+
368+ // Check the version.
369+ if ( semver . satisfies ( localESLintVersion , requiredESLintVersionRange ) ) {
370+ answers . installESLint = false ;
371+ return false ;
372+ }
373+
374+ return true ;
375+ }
376+
287377/* istanbul ignore next: no need to test inquirer*/
288378/**
289379 * Ask use a few questions on command prompt
@@ -346,6 +436,21 @@ function promptUser() {
346436 when ( answers ) {
347437 return ( ( answers . source === "guide" && answers . packageJsonExists ) || answers . source === "auto" ) ;
348438 }
439+ } ,
440+ {
441+ type : "confirm" ,
442+ name : "installESLint" ,
443+ message ( answers ) {
444+ const verb = semver . ltr ( answers . localESLintVersion , answers . requiredESLintVersionRange )
445+ ? "upgrade"
446+ : "downgrade" ;
447+
448+ return `The style guide "${ answers . styleguide } " requires eslint@${ answers . requiredESLintVersionRange } . You are currently using eslint@${ answers . localESLintVersion } .\n Do you want to ${ verb } ?` ;
449+ } ,
450+ default : true ,
451+ when ( answers ) {
452+ return answers . source === "guide" && answers . packageJsonExists && hasESLintVersionConflict ( answers ) ;
453+ }
349454 }
350455 ] ) . then ( earlyAnswers => {
351456
@@ -355,11 +460,14 @@ function promptUser() {
355460 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." ) ;
356461 return void 0 ;
357462 }
463+ if ( earlyAnswers . installESLint === false && ! semver . satisfies ( earlyAnswers . localESLintVersion , earlyAnswers . requiredESLintVersionRange ) ) {
464+ log . info ( `Note: it might not work since ESLint's version is mismatched with the ${ earlyAnswers . styleguide } config.` ) ;
465+ }
358466 if ( earlyAnswers . styleguide === "airbnb" && ! earlyAnswers . airbnbReact ) {
359467 earlyAnswers . styleguide = "airbnb-base" ;
360468 }
361469
362- config = getConfigForStyleGuide ( earlyAnswers . styleguide ) ;
470+ config = getConfigForStyleGuide ( earlyAnswers . styleguide , earlyAnswers . installESLint ) ;
363471 writeFile ( config , earlyAnswers . format ) ;
364472
365473 return void 0 ;
@@ -479,6 +587,7 @@ function promptUser() {
479587
480588const init = {
481589 getConfigForStyleGuide,
590+ hasESLintVersionConflict,
482591 processAnswers,
483592 /* istanbul ignore next */ initializeConfig ( ) {
484593 return promptUser ( ) ;
0 commit comments