12
12
const util = require ( "util" ) ,
13
13
inquirer = require ( "inquirer" ) ,
14
14
ProgressBar = require ( "progress" ) ,
15
+ semver = require ( "semver" ) ,
15
16
autoconfig = require ( "./autoconfig.js" ) ,
16
17
ConfigFile = require ( "./config-file" ) ,
17
18
ConfigOps = require ( "./config-ops" ) ,
18
19
getSourceCodeOfFiles = require ( "../util/source-code-util" ) . getSourceCodeOfFiles ,
20
+ ModuleResolver = require ( "../util/module-resolver" ) ,
19
21
npmUtil = require ( "../util/npm-util" ) ,
20
22
recConfig = require ( "../../conf/eslint-recommended" ) ,
21
23
log = require ( "../logging" ) ;
@@ -56,12 +58,35 @@ function writeFile(config, format) {
56
58
}
57
59
}
58
60
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
+
59
83
/**
60
84
* Synchronously install necessary plugins, configs, parsers, etc. based on the config
61
85
* @param {Object } config config object
86
+ * @param {boolean } [installESLint=true] If `false` is given, it does not install eslint.
62
87
* @returns {void }
63
88
*/
64
- function installModules ( config ) {
89
+ function installModules ( config , installESLint ) {
65
90
const modules = { } ;
66
91
67
92
// Create a list of modules which should be installed based on config
@@ -73,11 +98,10 @@ function installModules(config) {
73
98
if ( config . extends && config . extends . indexOf ( "eslint:" ) === - 1 ) {
74
99
const moduleName = `eslint-config-${ config . extends } ` ;
75
100
76
- log . info ( `Checking peerDependencies of ${ moduleName } ` ) ;
77
101
modules [ moduleName ] = "latest" ;
78
102
Object . assign (
79
103
modules ,
80
- npmUtil . fetchPeerDependencies ( `${ moduleName } @latest` )
104
+ getPeerDependencies ( `${ moduleName } @latest` )
81
105
) ;
82
106
}
83
107
@@ -86,15 +110,17 @@ function installModules(config) {
86
110
return ;
87
111
}
88
112
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" ] ) ;
94
117
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
+ }
98
124
}
99
125
100
126
// Install packages
@@ -265,9 +291,10 @@ function processAnswers(answers) {
265
291
/**
266
292
* process user's style guide of choice and return an appropriate config object.
267
293
* @param {string } guide name of the chosen style guide
294
+ * @param {boolean } [installESLint=true] If `false` is given, it does not install eslint.
268
295
* @returns {Object } config object
269
296
*/
270
- function getConfigForStyleGuide ( guide ) {
297
+ function getConfigForStyleGuide ( guide , installESLint ) {
271
298
const guides = {
272
299
google : { extends : "google" } ,
273
300
airbnb : { extends : "airbnb" } ,
@@ -279,11 +306,74 @@ function getConfigForStyleGuide(guide) {
279
306
throw new Error ( "You referenced an unsupported guide." ) ;
280
307
}
281
308
282
- installModules ( guides [ guide ] ) ;
309
+ installModules ( guides [ guide ] , installESLint ) ;
283
310
284
311
return guides [ guide ] ;
285
312
}
286
313
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
+
287
377
/* istanbul ignore next: no need to test inquirer*/
288
378
/**
289
379
* Ask use a few questions on command prompt
@@ -346,6 +436,21 @@ function promptUser() {
346
436
when ( answers ) {
347
437
return ( ( answers . source === "guide" && answers . packageJsonExists ) || answers . source === "auto" ) ;
348
438
}
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
+ }
349
454
}
350
455
] ) . then ( earlyAnswers => {
351
456
@@ -355,11 +460,14 @@ function promptUser() {
355
460
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." ) ;
356
461
return void 0 ;
357
462
}
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
+ }
358
466
if ( earlyAnswers . styleguide === "airbnb" && ! earlyAnswers . airbnbReact ) {
359
467
earlyAnswers . styleguide = "airbnb-base" ;
360
468
}
361
469
362
- config = getConfigForStyleGuide ( earlyAnswers . styleguide ) ;
470
+ config = getConfigForStyleGuide ( earlyAnswers . styleguide , earlyAnswers . installESLint ) ;
363
471
writeFile ( config , earlyAnswers . format ) ;
364
472
365
473
return void 0 ;
@@ -479,6 +587,7 @@ function promptUser() {
479
587
480
588
const init = {
481
589
getConfigForStyleGuide,
590
+ hasESLintVersionConflict,
482
591
processAnswers,
483
592
/* istanbul ignore next */ initializeConfig ( ) {
484
593
return promptUser ( ) ;
0 commit comments