Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
246 changes: 118 additions & 128 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 @@ -155,67 +117,119 @@ export class ConfigGenerator {
const extensions = `**/*.{${getExtensions(this.answers)}}`;

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;
const languages = this.answers.languages ?? ["javascript"];
const purpose = this.answers.purpose;
const useTs = this.answers.useTs;

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}"
};

exportContent += ` { files: ["${extensions}"], languageOptions: { ${envContent[this.answers.env.join(",")]} } },\n`;
}

if (this.answers.purpose === "syntax") {
if (purpose === "syntax") {

// no need to install any plugin
} else if (this.answers.purpose === "problems") {
} else 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.language === "typescript") {
this.result.devDependencies.push("typescript-eslint");
importContent += "import tseslint from \"typescript-eslint\";\n";
exportContent += " tseslint.configs.recommended,\n";
// language = javascript/typescript
if (languages.includes("javascript")) {
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}"
};

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 (this.answers.language === "typescript") {
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";
}

if (needCompatHelper) {
this.result.devDependencies.push("@eslint/eslintrc", "@eslint/js");
}
} else {
exportContent += " {ignores: [\"**/*.js\", \"**/*.cjs\", \"**/*.mjs\"]},\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";
// 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\"], language: \"json/json\", ...json.configs.recommended},\n";

exportContent += config;
}
if (languages.includes("jsonc")) {
const config = purpose === "syntax"
? " { files: [\"**/*.jsonc\"], plugins: { json }, language: \"json/jsonc\" },\n"
: " { files: [\"**/*.jsonc\"], language: \"json/jsonc\", ...json.configs.recommended },\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";
exportContent += config;
}
if (languages.includes("json5")) {
const config = purpose === "syntax"
? " { files: [\"**/*.json5\"], plugins: { json }, language: \"json/json5\" },\n"
: " { files: [\"**/*.json5\"], language: \"json/json5\", ...json.configs.recommended },\n";

exportContent += config;
}
}

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 = markdown
if (languages.includes("md")) {
this.result.devDependencies.push("@eslint/markdown");
importContent += "import markdown from \"@eslint/markdown\";\n";

if (purpose === "syntax") {
const config = this.answers.mdType === "commonmark"
? " { files: [\"**/*.md\"], plugins: {markdown}, language: \"markdown/commonmark\" },\n"
: " { files: [\"**/*.md\"], plugins: {markdown}, language: \"markdown/gfm\" },\n";

exportContent += config;
} else if (purpose === "problems") {
exportContent += " ...markdown.configs.recommended,\n";

if (this.answers.mdType === "gfm") {

// the default is commonmark
exportContent += " { files: [\"**/*.md\"], language: \"markdown/gfm\" },\n";
}
}
}

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

Expand Down Expand Up @@ -252,14 +266,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 +280,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