@@ -15,6 +15,7 @@ import enquirer from "enquirer";
15
15
import { isPackageTypeModule , installSyncSaveDev , fetchPeerDependencies , findPackageJson } from "./utils/npm-utils.js" ;
16
16
import { getShorthandName } from "./utils/naming.js" ;
17
17
import * as log from "./utils/logging.js" ;
18
+ import { langQuestions , jsQuestions , mdQuestions , installationQuestions } from "./questions.js" ;
18
19
19
20
//-----------------------------------------------------------------------------
20
21
// Helpers
@@ -28,7 +29,7 @@ import * as log from "./utils/logging.js";
28
29
function getExtensions ( answers ) {
29
30
const extensions = [ "js" , "mjs" , "cjs" ] ;
30
31
31
- if ( answers . language === "typescript" ) {
32
+ if ( answers . useTs ) {
32
33
extensions . push ( "ts" ) ;
33
34
}
34
35
@@ -39,14 +40,25 @@ function getExtensions(answers) {
39
40
if ( answers . framework === "react" ) {
40
41
extensions . push ( "jsx" ) ;
41
42
42
- if ( answers . language === "typescript" ) {
43
+ if ( answers . useTs ) {
43
44
extensions . push ( "tsx" ) ;
44
45
}
45
46
}
46
47
47
48
return extensions ;
48
49
}
49
50
51
+ const helperContent = `import path from "node:path";
52
+ import { fileURLToPath } from "node:url";
53
+ import { FlatCompat } from "@eslint/eslintrc";
54
+ import js from "@eslint/js";
55
+
56
+ // mimic CommonJS variables -- not needed if using CommonJS
57
+ const __filename = fileURLToPath(import.meta.url);
58
+ const __dirname = path.dirname(__filename);
59
+ const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});
60
+ ` ;
61
+
50
62
//-----------------------------------------------------------------------------
51
63
// Exports
52
64
//-----------------------------------------------------------------------------
@@ -80,65 +92,15 @@ export class ConfigGenerator {
80
92
* @returns {void }
81
93
*/
82
94
async prompt ( ) {
83
- const questions = [
84
- {
85
- type : "select" ,
86
- name : "purpose" ,
87
- message : "How would you like to use ESLint?" ,
88
- initial : 1 ,
89
- choices : [
90
- { message : "To check syntax only" , name : "syntax" } ,
91
- { message : "To check syntax and find problems" , name : "problems" }
92
- ]
93
- } ,
94
- {
95
- type : "select" ,
96
- name : "moduleType" ,
97
- message : "What type of modules does your project use?" ,
98
- initial : 0 ,
99
- choices : [
100
- { message : "JavaScript modules (import/export)" , name : "esm" } ,
101
- { message : "CommonJS (require/exports)" , name : "commonjs" } ,
102
- { message : "None of these" , name : "script" }
103
- ]
104
- } ,
105
- {
106
- type : "select" ,
107
- name : "framework" ,
108
- message : "Which framework does your project use?" ,
109
- initial : 0 ,
110
- choices : [
111
- { message : "React" , name : "react" } ,
112
- { message : "Vue.js" , name : "vue" } ,
113
- { message : "None of these" , name : "none" }
114
- ]
115
- } ,
116
- {
117
- type : "select" ,
118
- name : "language" ,
119
- message : "Does your project use TypeScript?" ,
120
- choices : [
121
- { message : "No" , name : "javascript" } ,
122
- { message : "Yes" , name : "typescript" }
123
- ] ,
124
- initial : 0
125
- } ,
126
- {
127
- type : "multiselect" ,
128
- name : "env" ,
129
- message : "Where does your code run?" ,
130
- hint : "(Press <space> to select, <a> to toggle all, <i> to invert selection)" ,
131
- initial : 0 ,
132
- choices : [
133
- { message : "Browser" , name : "browser" } ,
134
- { message : "Node" , name : "node" }
135
- ]
136
- }
137
- ] ;
95
+ Object . assign ( this . answers , await enquirer . prompt ( langQuestions ) ) ;
138
96
139
- const answers = await enquirer . prompt ( questions ) ;
97
+ if ( this . answers . languages . includes ( "javascript" ) ) {
98
+ Object . assign ( this . answers , await enquirer . prompt ( jsQuestions ) ) ;
99
+ }
140
100
141
- Object . assign ( this . answers , answers ) ;
101
+ if ( this . answers . languages . includes ( "md" ) ) {
102
+ Object . assign ( this . answers , await enquirer . prompt ( mdQuestions ) ) ;
103
+ }
142
104
}
143
105
144
106
/**
@@ -152,70 +114,121 @@ export class ConfigGenerator {
152
114
this . answers . config = typeof this . answers . config === "string"
153
115
? { packageName : this . answers . config , type : "flat" }
154
116
: this . answers . config ;
117
+
155
118
const extensions = `**/*.{${ getExtensions ( this . answers ) } }` ;
119
+ const languages = this . answers . languages ?? [ "javascript" ] ;
120
+ const purpose = this . answers . purpose ;
156
121
157
122
let importContent = "import { defineConfig } from \"eslint/config\";\n" ;
158
- const helperContent = `import path from "node:path";
159
- import { fileURLToPath } from "node:url";
160
- import { FlatCompat } from "@eslint/eslintrc";
161
- import js from "@eslint/js";
162
-
163
- // mimic CommonJS variables -- not needed if using CommonJS
164
- const __filename = fileURLToPath(import.meta.url);
165
- const __dirname = path.dirname(__filename);
166
- const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});
167
- ` ;
168
123
let exportContent = "" ;
169
124
let needCompatHelper = false ;
170
125
171
- if ( this . answers . moduleType === "commonjs" || this . answers . moduleType === "script" ) {
172
- exportContent += ` { files: ["**/*.js"], languageOptions: { sourceType: "${ this . answers . moduleType } " } },\n` ;
173
- }
126
+ // language = javascript/typescript
127
+ if ( languages . includes ( "javascript" ) ) {
174
128
175
- if ( this . answers . env ?. length > 0 ) {
176
- this . result . devDependencies . push ( "globals" ) ;
177
- importContent += "import globals from \"globals\";\n" ;
178
- const envContent = {
179
- browser : "globals: globals.browser" ,
180
- node : "globals: globals.node" ,
181
- "browser,node" : "globals: {...globals.browser, ...globals.node}"
182
- } ;
129
+ const useTs = this . answers . useTs ;
183
130
184
- exportContent += ` { files: ["${ extensions } "], languageOptions: { ${ envContent [ this . answers . env . join ( "," ) ] } } },\n` ;
185
- }
131
+ if ( purpose === "problems" ) {
132
+ this . result . devDependencies . push ( "@eslint/js" ) ;
133
+ importContent += "import js from \"@eslint/js\";\n" ;
134
+ exportContent += ` { files: ["${ extensions } "], plugins: { js }, extends: ["js/recommended"] },\n` ;
135
+ }
186
136
187
- if ( this . answers . purpose === "syntax" ) {
137
+ if ( this . answers . moduleType === "commonjs" || this . answers . moduleType === "script" ) {
138
+ exportContent += ` { files: ["**/*.js"], languageOptions: { sourceType: "${ this . answers . moduleType } " } },\n` ;
139
+ }
140
+
141
+ if ( this . answers . env ?. length > 0 ) {
142
+ this . result . devDependencies . push ( "globals" ) ;
143
+ importContent += "import globals from \"globals\";\n" ;
144
+ const envContent = {
145
+ browser : "globals: globals.browser" ,
146
+ node : "globals: globals.node" ,
147
+ "browser,node" : "globals: {...globals.browser, ...globals.node}"
148
+ } ;
188
149
189
- // no need to install any plugin
190
- } else if ( this . answers . purpose === "problems" ) {
191
- this . result . devDependencies . push ( "@eslint/js" ) ;
192
- importContent += "import js from \"@eslint/js\";\n" ;
193
- exportContent += ` { files: ["${ extensions } "], plugins: { js }, extends: ["js/recommended"] },\n` ;
150
+ exportContent += ` { files: ["${ extensions } "], languageOptions: { ${ envContent [ this . answers . env . join ( "," ) ] } } },\n` ;
151
+ }
152
+ if ( useTs ) {
153
+ this . result . devDependencies . push ( "typescript-eslint" ) ;
154
+ importContent += "import tseslint from \"typescript-eslint\";\n" ;
155
+ exportContent += " tseslint.configs.recommended,\n" ;
156
+ }
157
+
158
+ if ( this . answers . framework === "vue" ) {
159
+ this . result . devDependencies . push ( "eslint-plugin-vue" ) ;
160
+ importContent += "import pluginVue from \"eslint-plugin-vue\";\n" ;
161
+ exportContent += " pluginVue.configs[\"flat/essential\"],\n" ;
162
+
163
+ // https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
164
+ if ( useTs ) {
165
+ exportContent += " { files: [\"**/*.vue\"], languageOptions: { parserOptions: { parser: tseslint.parser } } },\n" ;
166
+ }
167
+ }
168
+
169
+ if ( this . answers . framework === "react" ) {
170
+ this . result . devDependencies . push ( "eslint-plugin-react" ) ;
171
+ importContent += "import pluginReact from \"eslint-plugin-react\";\n" ;
172
+ exportContent += " pluginReact.configs.flat.recommended,\n" ;
173
+ }
174
+
175
+ } else {
176
+ exportContent += " { ignores: [\"**/*.js\", \"**/*.cjs\", \"**/*.mjs\"] },\n" ;
194
177
}
195
178
196
- if ( this . answers . language === "typescript" ) {
197
- this . result . devDependencies . push ( "typescript-eslint" ) ;
198
- importContent += "import tseslint from \"typescript-eslint\";\n" ;
199
- exportContent += " tseslint.configs.recommended,\n" ;
179
+ // language = json/jsonc/json5
180
+ if ( languages . some ( item => item . startsWith ( "json" ) ) ) {
181
+ this . result . devDependencies . push ( "@eslint/json" ) ;
182
+ importContent += "import json from \"@eslint/json\";\n" ;
183
+
184
+ if ( languages . includes ( "json" ) ) {
185
+ const config = purpose === "syntax"
186
+ ? " { files: [\"**/*.json\"], plugins: { json }, language: \"json/json\" },\n"
187
+ : " { files: [\"**/*.json\"], plugins: { json }, language: \"json/json\", extends: [\"json/recommended\"] },\n" ;
188
+
189
+ exportContent += config ;
190
+ }
191
+ if ( languages . includes ( "jsonc" ) ) {
192
+ const config = purpose === "syntax"
193
+ ? " { files: [\"**/*.jsonc\"], plugins: { json }, language: \"json/jsonc\" },\n"
194
+ : " { files: [\"**/*.jsonc\"], plugins: { json }, language: \"json/jsonc\", extends: [\"json/recommended\"] },\n" ;
195
+
196
+ exportContent += config ;
197
+ }
198
+ if ( languages . includes ( "json5" ) ) {
199
+ const config = purpose === "syntax"
200
+ ? " { files: [\"**/*.json5\"], plugins: { json }, language: \"json/json5\" },\n"
201
+ : " { files: [\"**/*.json5\"], plugins: { json }, language: \"json/json5\", extends: [\"json/recommended\"] },\n" ;
202
+
203
+ exportContent += config ;
204
+ }
200
205
}
201
206
202
- if ( this . answers . framework === "vue" ) {
203
- this . result . devDependencies . push ( "eslint-plugin-vue" ) ;
204
- importContent += "import pluginVue from \" eslint-plugin-vue\";\n" ;
205
- exportContent += " pluginVue.configs[\"flat/essential\"], \n" ;
207
+ // language = markdown
208
+ if ( languages . includes ( "md" ) ) {
209
+ this . result . devDependencies . push ( "@ eslint/markdown" ) ;
210
+ importContent += "import markdown from \"@eslint/markdown\"; \n" ;
206
211
207
- // https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
208
- if ( this . answers . language === "typescript" ) {
209
- exportContent += " { files: [\"**/*.vue\"], languageOptions: { parserOptions: { parser: tseslint.parser } } },\n" ;
212
+ if ( purpose === "syntax" ) {
213
+ exportContent += ` { files: ["**/*.md"], plugins: { markdown }, language: "markdown/${ this . answers . mdType } " },\n` ;
214
+ } else if ( purpose === "problems" ) {
215
+ exportContent += ` { files: ["**/*.md"], plugins: { markdown }, language: "markdown/${ this . answers . mdType } ", extends: ["markdown/recommended"] },\n` ;
210
216
}
211
217
}
212
218
213
- if ( this . answers . framework === "react" ) {
214
- this . result . devDependencies . push ( "eslint-plugin-react" ) ;
215
- importContent += "import pluginReact from \"eslint-plugin-react\";\n" ;
216
- exportContent += " pluginReact.configs.flat.recommended,\n" ;
219
+ // language = css
220
+ if ( languages . includes ( "css" ) ) {
221
+ this . result . devDependencies . push ( "@eslint/css" ) ;
222
+ importContent += "import css from \"@eslint/css\";\n" ;
223
+
224
+ if ( purpose === "syntax" ) {
225
+ exportContent += " { files: [\"**/*.css\"], plugins: { css }, language: \"css/css\" },\n" ;
226
+ } else if ( purpose === "problems" ) {
227
+ exportContent += " { files: [\"**/*.css\"], plugins: { css }, language: \"css/css\", extends: [\"css/recommended\"] },\n" ;
228
+ }
217
229
}
218
230
231
+ // passed `--config`
219
232
if ( this . answers . config ) {
220
233
const config = this . answers . config ;
221
234
@@ -255,11 +268,6 @@ const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.c
255
268
if ( needCompatHelper ) {
256
269
this . result . devDependencies . push ( "@eslint/eslintrc" , "@eslint/js" ) ;
257
270
}
258
-
259
- const lintFilesConfig = ` { files: ["${ extensions } "] },\n` ;
260
-
261
- exportContent = `${ lintFilesConfig } ${ exportContent } ` ;
262
-
263
271
this . result . configContent = `${ importContent }
264
272
${ needCompatHelper ? helperContent : "" }
265
273
export default defineConfig([\n${ exportContent || " {}\n" } ]);` ; // defaults to `[{}]` to avoid empty config warning
@@ -274,24 +282,8 @@ export default defineConfig([\n${exportContent || " {}\n"}]);`; // defaults to
274
282
log . info ( "The config that you've selected requires the following dependencies:\n" ) ;
275
283
log . info ( this . result . devDependencies . join ( ", " ) ) ;
276
284
277
- const questions = [ {
278
- type : "toggle" ,
279
- name : "executeInstallation" ,
280
- message : "Would you like to install them now?" ,
281
- enabled : "Yes" ,
282
- disabled : "No" ,
283
- initial : 1
284
- } , {
285
- type : "select" ,
286
- name : "packageManager" ,
287
- message : "Which package manager do you want to use?" ,
288
- initial : 0 ,
289
- choices : [ "npm" , "yarn" , "pnpm" , "bun" ] ,
290
- skip ( ) {
291
- return this . state . answers . executeInstallation === false ;
292
- }
293
- } ] ;
294
- const { executeInstallation, packageManager } = ( await enquirer . prompt ( questions ) ) ;
285
+
286
+ const { executeInstallation, packageManager } = ( await enquirer . prompt ( installationQuestions ) ) ;
295
287
const configPath = path . join ( this . cwd , this . result . configFilename ) ;
296
288
297
289
if ( executeInstallation === true ) {
0 commit comments