@@ -15,6 +15,7 @@ import enquirer from "enquirer";
1515import { isPackageTypeModule , installSyncSaveDev , fetchPeerDependencies , findPackageJson } from "./utils/npm-utils.js" ;
1616import { getShorthandName } from "./utils/naming.js" ;
1717import * as log from "./utils/logging.js" ;
18+ import { langQuestions , jsQuestions , mdQuestions , installationQuestions } from "./questions.js" ;
1819
1920//-----------------------------------------------------------------------------
2021// Helpers
@@ -28,7 +29,7 @@ import * as log from "./utils/logging.js";
2829function getExtensions ( answers ) {
2930 const extensions = [ "js" , "mjs" , "cjs" ] ;
3031
31- if ( answers . language === "typescript" ) {
32+ if ( answers . useTs ) {
3233 extensions . push ( "ts" ) ;
3334 }
3435
@@ -39,14 +40,25 @@ function getExtensions(answers) {
3940 if ( answers . framework === "react" ) {
4041 extensions . push ( "jsx" ) ;
4142
42- if ( answers . language === "typescript" ) {
43+ if ( answers . useTs ) {
4344 extensions . push ( "tsx" ) ;
4445 }
4546 }
4647
4748 return extensions ;
4849}
4950
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+
5062//-----------------------------------------------------------------------------
5163// Exports
5264//-----------------------------------------------------------------------------
@@ -80,65 +92,15 @@ export class ConfigGenerator {
8092 * @returns {void }
8193 */
8294 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 ) ) ;
13896
139- const answers = await enquirer . prompt ( questions ) ;
97+ if ( this . answers . languages . includes ( "javascript" ) ) {
98+ Object . assign ( this . answers , await enquirer . prompt ( jsQuestions ) ) ;
99+ }
140100
141- Object . assign ( this . answers , answers ) ;
101+ if ( this . answers . languages . includes ( "md" ) ) {
102+ Object . assign ( this . answers , await enquirer . prompt ( mdQuestions ) ) ;
103+ }
142104 }
143105
144106 /**
@@ -152,70 +114,121 @@ export class ConfigGenerator {
152114 this . answers . config = typeof this . answers . config === "string"
153115 ? { packageName : this . answers . config , type : "flat" }
154116 : this . answers . config ;
117+
155118 const extensions = `**/*.{${ getExtensions ( this . answers ) } }` ;
119+ const languages = this . answers . languages ?? [ "javascript" ] ;
120+ const purpose = this . answers . purpose ;
156121
157122 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- ` ;
168123 let exportContent = "" ;
169124 let needCompatHelper = false ;
170125
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" ) ) {
174128
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 ;
183130
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+ }
186136
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+ } ;
188149
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" ;
194177 }
195178
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+ }
200205 }
201206
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" ;
206211
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` ;
210216 }
211217 }
212218
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+ }
217229 }
218230
231+ // passed `--config`
219232 if ( this . answers . config ) {
220233 const config = this . answers . config ;
221234
@@ -255,11 +268,6 @@ const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.c
255268 if ( needCompatHelper ) {
256269 this . result . devDependencies . push ( "@eslint/eslintrc" , "@eslint/js" ) ;
257270 }
258-
259- const lintFilesConfig = ` { files: ["${ extensions } "] },\n` ;
260-
261- exportContent = `${ lintFilesConfig } ${ exportContent } ` ;
262-
263271 this . result . configContent = `${ importContent }
264272${ needCompatHelper ? helperContent : "" }
265273export default defineConfig([\n${ exportContent || " {}\n" } ]);` ; // defaults to `[{}]` to avoid empty config warning
@@ -274,24 +282,8 @@ export default defineConfig([\n${exportContent || " {}\n"}]);`; // defaults to
274282 log . info ( "The config that you've selected requires the following dependencies:\n" ) ;
275283 log . info ( this . result . devDependencies . join ( ", " ) ) ;
276284
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 ) ) ;
295287 const configPath = path . join ( this . cwd , this . result . configFilename ) ;
296288
297289 if ( executeInstallation === true ) {
0 commit comments