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
83 changes: 57 additions & 26 deletions lib/config-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
* @fileoverview to generate config files.
* @author 唯然<weiran.zsd@outlook.com>
*/

//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------

import process from "node:process";
import path from "node:path";
import { spawnSync } from "node:child_process";
Expand All @@ -11,6 +16,41 @@ import { isPackageTypeModule, installSyncSaveDev, fetchPeerDependencies, findPac
import { getShorthandName } from "./utils/naming.js";
import * as log from "./utils/logging.js";

//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------

/**
* Get the file extensions to lint based on the user's answers.
* @param {Object} answers The answers provided by the user.
* @returns {string[]} The file extensions to lint.
*/
function getExtensions(answers) {
const extensions = ["js", "mjs", "cjs"];

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

if (answers.framework === "vue") {
extensions.push("vue");
}

if (answers.framework === "react") {
extensions.push("jsx");

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

return extensions;
}

//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------

/**
* Class representing a ConfigGenerator.
*/
Expand Down Expand Up @@ -112,24 +152,24 @@ export class ConfigGenerator {
this.answers.config = typeof this.answers.config === "string"
? { packageName: this.answers.config, type: "flat" }
: this.answers.config;
const extensions = []; // file extensions to lint, the default is ["js", "mjs", "cjs"]
const extensions = `**/*.{${getExtensions(this.answers)}}`;

let importContent = "";
const helperContent = `import path from "path";
import { fileURLToPath } from "url";
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 pluginJs from "@eslint/js";
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: pluginJs.configs.recommended});
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`;
exportContent += ` { files: ["**/*.js"], languageOptions: { sourceType: "${this.answers.moduleType}" } },\n`;
}

if (this.answers.env?.length > 0) {
Expand All @@ -141,44 +181,36 @@ const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: plug
"browser,node": "globals: {...globals.browser, ...globals.node}"
};

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

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

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

if (this.answers.language === "typescript") {
extensions.push("ts");
this.result.devDependencies.push("typescript-eslint");
importContent += "import tseslint from \"typescript-eslint\";\n";
exportContent += " ...tseslint.configs.recommended,\n";
exportContent += " tseslint.configs.recommended,\n";
}

if (this.answers.framework === "vue") {
extensions.push("vue");
this.result.devDependencies.push("eslint-plugin-vue");
importContent += "import pluginVue from \"eslint-plugin-vue\";\n";
exportContent += " ...pluginVue.configs[\"flat/essential\"],\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";
exportContent += " { files: [\"**/*.vue\"], languageOptions: { parserOptions: { parser: tseslint.parser } } },\n";
}
}

if (this.answers.framework === "react") {
extensions.push("jsx");

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

this.result.devDependencies.push("eslint-plugin-react");
importContent += "import pluginReact from \"eslint-plugin-react\";\n";
exportContent += " pluginReact.configs.flat.recommended,\n";
Expand Down Expand Up @@ -210,28 +242,27 @@ const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: plug

if (config.type === "flat" || config.type === void 0) {
importContent += `import config from "${config.packageName}";\n`;
exportContent += " ...[].concat(config),\n";
exportContent += " config,\n";
} else if (config.type === "eslintrc") {
needCompatHelper = true;

const shorthandName = getShorthandName(config.packageName, "eslint-config");

exportContent += ` ...compat.extends("${shorthandName}"),\n`;
exportContent += ` compat.extends("${shorthandName}"),\n`;
}
}

if (needCompatHelper) {
this.result.devDependencies.push("@eslint/eslintrc", "@eslint/js");
}

const lintFilesConfig = extensions.length > 0 ? ` {files: ["**/*.{${["js", "mjs", "cjs", ...extensions]}}"]},\n` : "";
const lintFilesConfig = ` { files: ["${extensions}"] },\n`;

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

this.result.configContent = `${importContent}
${needCompatHelper ? helperContent : ""}
/** @type {import('eslint').Linter.Config[]} */
export default [\n${exportContent || " {}\n"}];`; // defaults to `[{}]` to avoid empty config warning
export default defineConfig([\n${exportContent || " {}\n"}]);`; // defaults to `[{}]` to avoid empty config warning
}

/**
Expand Down
19 changes: 10 additions & 9 deletions tests/__snapshots__/config@eslint-config-airbnb
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"configContent": "
import path from "path";
import { fileURLToPath } from "url";
"configContent": "import { defineConfig } from "eslint/config";

import path from "node:path";
import { fileURLToPath } from "node:url";
import { FlatCompat } from "@eslint/eslintrc";
import pluginJs from "@eslint/js";
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: pluginJs.configs.recommended});
const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});

/** @type {import('eslint').Linter.Config[]} */
export default [
...compat.extends("airbnb"),
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
compat.extends("airbnb"),
]);",
"configFilename": "eslint.config.mjs",
"devDependencies": [
"eslint@^7.32.0 || ^8.2.0",
Expand Down
19 changes: 10 additions & 9 deletions tests/__snapshots__/config@eslint-config-airbnb-base
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"configContent": "
import path from "path";
import { fileURLToPath } from "url";
"configContent": "import { defineConfig } from "eslint/config";

import path from "node:path";
import { fileURLToPath } from "node:url";
import { FlatCompat } from "@eslint/eslintrc";
import pluginJs from "@eslint/js";
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: pluginJs.configs.recommended});
const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});

/** @type {import('eslint').Linter.Config[]} */
export default [
...compat.extends("airbnb-base"),
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
compat.extends("airbnb-base"),
]);",
"configFilename": "eslint.config.mjs",
"devDependencies": [
"eslint@^7.32.0 || ^8.2.0",
Expand Down
19 changes: 10 additions & 9 deletions tests/__snapshots__/config@eslint-config-standard
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
{
"configContent": "
import path from "path";
import { fileURLToPath } from "url";
"configContent": "import { defineConfig } from "eslint/config";

import path from "node:path";
import { fileURLToPath } from "node:url";
import { FlatCompat } from "@eslint/eslintrc";
import pluginJs from "@eslint/js";
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: pluginJs.configs.recommended});
const compat = new FlatCompat({baseDirectory: __dirname, recommendedConfig: js.configs.recommended});

/** @type {import('eslint').Linter.Config[]} */
export default [
...compat.extends("standard"),
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
compat.extends("standard"),
]);",
"configFilename": "eslint.config.mjs",
"devDependencies": [
"eslint@^8.0.1",
Expand Down
11 changes: 6 additions & 5 deletions tests/__snapshots__/config@eslint-config-standard-flat
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"configContent": "import config from "eslint-config-standard";
"configContent": "import { defineConfig } from "eslint/config";
import config from "eslint-config-standard";


/** @type {import('eslint').Linter.Config[]} */
export default [
...[].concat(config),
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
config,
]);",
"configFilename": "eslint.config.mjs",
"devDependencies": [
"eslint@^8.0.1",
Expand Down
11 changes: 6 additions & 5 deletions tests/__snapshots__/config@eslint-config-standard-flat2
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"configContent": "import config from "eslint-config-standard";
"configContent": "import { defineConfig } from "eslint/config";
import config from "eslint-config-standard";


/** @type {import('eslint').Linter.Config[]} */
export default [
...[].concat(config),
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
config,
]);",
"configFilename": "eslint.config.mjs",
"devDependencies": [
"eslint@^8.0.1",
Expand Down
11 changes: 6 additions & 5 deletions tests/__snapshots__/config@eslint-config-xo
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"configContent": "import config from "eslint-config-xo";
"configContent": "import { defineConfig } from "eslint/config";
import config from "eslint-config-xo";


/** @type {import('eslint').Linter.Config[]} */
export default [
...[].concat(config),
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
config,
]);",
"configFilename": "eslint.config.mjs",
"devDependencies": [
"eslint@>=9.8.0",
Expand Down
10 changes: 5 additions & 5 deletions tests/__snapshots__/empty
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
{
"configContent": "
"configContent": "import { defineConfig } from "eslint/config";

/** @type {import('eslint').Linter.Config[]} */
export default [
{}
];",

export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
]);",
"configFilename": "eslint.config.js",
"devDependencies": [
"eslint",
Expand Down
17 changes: 9 additions & 8 deletions tests/__snapshots__/problems-commonjs-none-javascript
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
{
"configContent": "import globals from "globals";
import pluginJs from "@eslint/js";
"configContent": "import { defineConfig } from "eslint/config";
import globals from "globals";
import js from "@eslint/js";


/** @type {import('eslint').Linter.Config[]} */
export default [
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
{languageOptions: { globals: {...globals.browser, ...globals.node} }},
pluginJs.configs.recommended,
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs}"] },
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
{ files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: {...globals.browser, ...globals.node} } },
{ files: ["**/*.{js,mjs,cjs}"], plugins: { js }, extends: ["js/recommended"] },
]);",
"configFilename": "eslint.config.js",
"devDependencies": [
"eslint",
Expand Down
20 changes: 10 additions & 10 deletions tests/__snapshots__/problems-commonjs-none-typescript
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
{
"configContent": "import globals from "globals";
import pluginJs from "@eslint/js";
"configContent": "import { defineConfig } from "eslint/config";
import globals from "globals";
import js from "@eslint/js";
import tseslint from "typescript-eslint";


/** @type {import('eslint').Linter.Config[]} */
export default [
{files: ["**/*.{js,mjs,cjs,ts}"]},
{files: ["**/*.js"], languageOptions: {sourceType: "commonjs"}},
{languageOptions: { globals: {...globals.browser, ...globals.node} }},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
];",
export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ files: ["**/*.js"], languageOptions: { sourceType: "commonjs" } },
{ files: ["**/*.{js,mjs,cjs,ts}"], languageOptions: { globals: {...globals.browser, ...globals.node} } },
{ files: ["**/*.{js,mjs,cjs,ts}"], plugins: { js }, extends: ["js/recommended"] },
tseslint.configs.recommended,
]);",
"configFilename": "eslint.config.js",
"devDependencies": [
"eslint",
Expand Down
Loading