Skip to content

Commit 886f39e

Browse files
feat: support languages json/markdown/css (#159)
* feat: support languages json/markdown fixes #148 resubmit #151 * fix: extensions for typescript files * refactor: language = js * fix: global ignores * fix: add css & use `extends` * fix: vue + ts * fix: review suggestions * Update lib/config-generator.js Co-authored-by: Milos Djermanovic <[email protected]> * Update lib/config-generator.js Co-authored-by: Milos Djermanovic <[email protected]> * Update lib/config-generator.js Co-authored-by: Milos Djermanovic <[email protected]> * Update lib/config-generator.js Co-authored-by: Milos Djermanovic <[email protected]> * fix: review suggestions * chore: update snapshots * Update lib/config-generator.js Co-authored-by: Milos Djermanovic <[email protected]> * chore: update snapshots * fix: review suggestions * fix: opt config for markdown --------- Co-authored-by: Milos Djermanovic <[email protected]>
1 parent c3f9ffd commit 886f39e

File tree

59 files changed

+533
-229
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+533
-229
lines changed

lib/config-generator.js

+118-126
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import enquirer from "enquirer";
1515
import { isPackageTypeModule, installSyncSaveDev, fetchPeerDependencies, findPackageJson } from "./utils/npm-utils.js";
1616
import { getShorthandName } from "./utils/naming.js";
1717
import * 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";
2829
function 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 : ""}
265273
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
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

Comments
 (0)