Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
257 changes: 129 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,130 @@ 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 (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";
}

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

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

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

// 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;
} else if (purpose === "problems") {
exportContent += " extends: [\"markdown/recommended\"],\n";

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

// the default is commonmark
exportContent += " { files: [\"**/*.md\"], language: \"markdown/gfm\" },\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 (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\"], language: \"css/css\", extends: [\"css/recommended\"] },\n";
}
}

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

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