Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 118 additions & 126 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import enquirer from "enquirer";
import { isPackageTypeModule, installSyncSaveDev, fetchPeerDependencies, findPackageJson } from "./utils/npm-utils.js";
import { getShorthandName } from "./utils/naming.js";
import * as log from "./utils/logging.js";
import { langQuestions, jsQuestions, mdQuestions, installationQuestions } from "./questions.js";

//-----------------------------------------------------------------------------
// Helpers
Expand All @@ -28,7 +29,7 @@ import * as log from "./utils/logging.js";
function getExtensions(answers) {
const extensions = ["js", "mjs", "cjs"];

if (answers.language === "typescript") {
if (answers.useTs) {
extensions.push("ts");
}

Expand All @@ -39,14 +40,25 @@ function getExtensions(answers) {
if (answers.framework === "react") {
extensions.push("jsx");

if (answers.language === "typescript") {
if (answers.useTs) {
extensions.push("tsx");
}
}

return extensions;
}

const helperContent = `import path from "node:path";
import { fileURLToPath } from "node:url";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";

// mimic CommonJS variables -- not needed if using CommonJS
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});
`;

//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
Expand Down Expand Up @@ -80,65 +92,15 @@ export class ConfigGenerator {
* @returns {void}
*/
async prompt() {
const questions = [
{
type: "select",
name: "purpose",
message: "How would you like to use ESLint?",
initial: 1,
choices: [
{ message: "To check syntax only", name: "syntax" },
{ message: "To check syntax and find problems", name: "problems" }
]
},
{
type: "select",
name: "moduleType",
message: "What type of modules does your project use?",
initial: 0,
choices: [
{ message: "JavaScript modules (import/export)", name: "esm" },
{ message: "CommonJS (require/exports)", name: "commonjs" },
{ message: "None of these", name: "script" }
]
},
{
type: "select",
name: "framework",
message: "Which framework does your project use?",
initial: 0,
choices: [
{ message: "React", name: "react" },
{ message: "Vue.js", name: "vue" },
{ message: "None of these", name: "none" }
]
},
{
type: "select",
name: "language",
message: "Does your project use TypeScript?",
choices: [
{ message: "No", name: "javascript" },
{ message: "Yes", name: "typescript" }
],
initial: 0
},
{
type: "multiselect",
name: "env",
message: "Where does your code run?",
hint: "(Press <space> to select, <a> to toggle all, <i> to invert selection)",
initial: 0,
choices: [
{ message: "Browser", name: "browser" },
{ message: "Node", name: "node" }
]
}
];
Object.assign(this.answers, await enquirer.prompt(langQuestions));

const answers = await enquirer.prompt(questions);
if (this.answers.languages.includes("javascript")) {
Object.assign(this.answers, await enquirer.prompt(jsQuestions));
}

Object.assign(this.answers, answers);
if (this.answers.languages.includes("md")) {
Object.assign(this.answers, await enquirer.prompt(mdQuestions));
}
}

/**
Expand All @@ -152,70 +114,121 @@ export class ConfigGenerator {
this.answers.config = typeof this.answers.config === "string"
? { packageName: this.answers.config, type: "flat" }
: this.answers.config;

const extensions = `**/*.{${getExtensions(this.answers)}}`;
const languages = this.answers.languages ?? ["javascript"];
const purpose = this.answers.purpose;

let importContent = "import { defineConfig } from \"eslint/config\";\n";
const helperContent = `import path from "node:path";
import { fileURLToPath } from "node:url";
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";

// mimic CommonJS variables -- not needed if using CommonJS
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});
`;
let exportContent = "";
let needCompatHelper = false;

if (this.answers.moduleType === "commonjs" || this.answers.moduleType === "script") {
exportContent += ` { files: ["**/*.js"], languageOptions: { sourceType: "${this.answers.moduleType}" } },\n`;
}
// language = javascript/typescript
if (languages.includes("javascript")) {

if (this.answers.env?.length > 0) {
this.result.devDependencies.push("globals");
importContent += "import globals from \"globals\";\n";
const envContent = {
browser: "globals: globals.browser",
node: "globals: globals.node",
"browser,node": "globals: {...globals.browser, ...globals.node}"
};
const useTs = this.answers.useTs;

exportContent += ` { files: ["${extensions}"], languageOptions: { ${envContent[this.answers.env.join(",")]} } },\n`;
}
if (purpose === "problems") {
this.result.devDependencies.push("@eslint/js");
importContent += "import js from \"@eslint/js\";\n";
exportContent += ` { files: ["${extensions}"], plugins: { js }, extends: ["js/recommended"] },\n`;
}

if (this.answers.purpose === "syntax") {
if (this.answers.moduleType === "commonjs" || this.answers.moduleType === "script") {
exportContent += ` { files: ["**/*.js"], languageOptions: { sourceType: "${this.answers.moduleType}" } },\n`;
}

if (this.answers.env?.length > 0) {
this.result.devDependencies.push("globals");
importContent += "import globals from \"globals\";\n";
const envContent = {
browser: "globals: globals.browser",
node: "globals: globals.node",
"browser,node": "globals: {...globals.browser, ...globals.node}"
};

// no need to install any plugin
} else if (this.answers.purpose === "problems") {
this.result.devDependencies.push("@eslint/js");
importContent += "import js from \"@eslint/js\";\n";
exportContent += ` { files: ["${extensions}"], plugins: { js }, extends: ["js/recommended"] },\n`;
exportContent += ` { files: ["${extensions}"], languageOptions: { ${envContent[this.answers.env.join(",")]} } },\n`;
}
if (useTs) {
this.result.devDependencies.push("typescript-eslint");
importContent += "import tseslint from \"typescript-eslint\";\n";
exportContent += " tseslint.configs.recommended,\n";
}

if (this.answers.framework === "vue") {
this.result.devDependencies.push("eslint-plugin-vue");
importContent += "import pluginVue from \"eslint-plugin-vue\";\n";
exportContent += " pluginVue.configs[\"flat/essential\"],\n";

// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
if (useTs) {
exportContent += " { files: [\"**/*.vue\"], languageOptions: { parserOptions: { parser: tseslint.parser } } },\n";
}
}

if (this.answers.framework === "react") {
this.result.devDependencies.push("eslint-plugin-react");
importContent += "import pluginReact from \"eslint-plugin-react\";\n";
exportContent += " pluginReact.configs.flat.recommended,\n";
}

} else {
exportContent += " { ignores: [\"**/*.js\", \"**/*.cjs\", \"**/*.mjs\"] },\n";
}

if (this.answers.language === "typescript") {
this.result.devDependencies.push("typescript-eslint");
importContent += "import tseslint from \"typescript-eslint\";\n";
exportContent += " tseslint.configs.recommended,\n";
// language = json/jsonc/json5
if (languages.some(item => item.startsWith("json"))) {
this.result.devDependencies.push("@eslint/json");
importContent += "import json from \"@eslint/json\";\n";

if (languages.includes("json")) {
const config = purpose === "syntax"
? " { files: [\"**/*.json\"], plugins: { json }, language: \"json/json\" },\n"
: " { files: [\"**/*.json\"], plugins: { json }, language: \"json/json\", extends: [\"json/recommended\"] },\n";

exportContent += config;
}
if (languages.includes("jsonc")) {
const config = purpose === "syntax"
? " { files: [\"**/*.jsonc\"], plugins: { json }, language: \"json/jsonc\" },\n"
: " { files: [\"**/*.jsonc\"], plugins: { json }, language: \"json/jsonc\", extends: [\"json/recommended\"] },\n";

exportContent += config;
}
if (languages.includes("json5")) {
const config = purpose === "syntax"
? " { files: [\"**/*.json5\"], plugins: { json }, language: \"json/json5\" },\n"
: " { files: [\"**/*.json5\"], plugins: { json }, language: \"json/json5\", extends: [\"json/recommended\"] },\n";

exportContent += config;
}
}

if (this.answers.framework === "vue") {
this.result.devDependencies.push("eslint-plugin-vue");
importContent += "import pluginVue from \"eslint-plugin-vue\";\n";
exportContent += " pluginVue.configs[\"flat/essential\"],\n";
// language = markdown
if (languages.includes("md")) {
this.result.devDependencies.push("@eslint/markdown");
importContent += "import markdown from \"@eslint/markdown\";\n";

// https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
if (this.answers.language === "typescript") {
exportContent += " { files: [\"**/*.vue\"], languageOptions: { parserOptions: { parser: tseslint.parser } } },\n";
if (purpose === "syntax") {
exportContent += ` { files: ["**/*.md"], plugins: { markdown }, language: "markdown/${this.answers.mdType}" },\n`;
} else if (purpose === "problems") {
exportContent += ` { files: ["**/*.md"], plugins: { markdown }, language: "markdown/${this.answers.mdType}", extends: ["markdown/recommended"] },\n`;
}
}

if (this.answers.framework === "react") {
this.result.devDependencies.push("eslint-plugin-react");
importContent += "import pluginReact from \"eslint-plugin-react\";\n";
exportContent += " pluginReact.configs.flat.recommended,\n";
// language = css
if (languages.includes("css")) {
this.result.devDependencies.push("@eslint/css");
importContent += "import css from \"@eslint/css\";\n";

if (purpose === "syntax") {
exportContent += " { files: [\"**/*.css\"], plugins: { css }, language: \"css/css\" },\n";
} else if (purpose === "problems") {
exportContent += " { files: [\"**/*.css\"], plugins: { css }, language: \"css/css\", extends: [\"css/recommended\"] },\n";
}
}

// passed `--config`
if (this.answers.config) {
const config = this.answers.config;

Expand Down Expand Up @@ -255,11 +268,6 @@ const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.c
if (needCompatHelper) {
this.result.devDependencies.push("@eslint/eslintrc", "@eslint/js");
}

const lintFilesConfig = ` { files: ["${extensions}"] },\n`;

exportContent = `${lintFilesConfig}${exportContent}`;

this.result.configContent = `${importContent}
${needCompatHelper ? helperContent : ""}
export default defineConfig([\n${exportContent || " {}\n"}]);`; // defaults to `[{}]` to avoid empty config warning
Expand All @@ -274,24 +282,8 @@ export default defineConfig([\n${exportContent || " {}\n"}]);`; // defaults to
log.info("The config that you've selected requires the following dependencies:\n");
log.info(this.result.devDependencies.join(", "));

const questions = [{
type: "toggle",
name: "executeInstallation",
message: "Would you like to install them now?",
enabled: "Yes",
disabled: "No",
initial: 1
}, {
type: "select",
name: "packageManager",
message: "Which package manager do you want to use?",
initial: 0,
choices: ["npm", "yarn", "pnpm", "bun"],
skip() {
return this.state.answers.executeInstallation === false;
}
}];
const { executeInstallation, packageManager } = (await enquirer.prompt(questions));

const { executeInstallation, packageManager } = (await enquirer.prompt(installationQuestions));
const configPath = path.join(this.cwd, this.result.configFilename);

if (executeInstallation === true) {
Expand Down
Loading