@jimmy.codes/eslint-config
Version:
A simple, modern ESLint config that covers most use cases.
505 lines (481 loc) • 13.5 kB
JavaScript
import { i as GLOB_IGNORES, l as GLOB_TESTS, n as GLOB_CJS } from "./globs-C5FyFNuk.js";
import { a as hasReact, c as hasTestingLibrary, d as hasVitest, i as hasPlaywright, l as hasTypescript, n as hasJest, o as hasReactQuery, r as hasNext, s as hasStorybook, t as hasAstro } from "./has-dependency-lkRo7x2C.js";
import gitignoreConfig from "eslint-config-flat-gitignore";
import globals from "globals";
import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
import { configs, importX } from "eslint-plugin-import-x";
import nodePlugin from "eslint-plugin-n";
import eslint from "@eslint/js";
import jsdocPlugin from "eslint-plugin-jsdoc";
import perfectionist from "eslint-plugin-perfectionist";
import eslintConfigPrettier from "eslint-config-prettier/flat";
import * as regexpPlugin from "eslint-plugin-regexp";
import stylisticPlugin from "@stylistic/eslint-plugin";
import eslintPluginUnicorn from "eslint-plugin-unicorn";
//#region src/configs/commonjs.ts
const commonjsConfig = () => {
return [{
files: [GLOB_CJS],
languageOptions: { globals: globals.commonjs },
name: "jimmy.codes/commonjs"
}];
};
//#endregion
//#region src/rules/eslint-comments.ts
const eslintCommentsRules = {
...comments.recommended.rules,
"@eslint-community/eslint-comments/no-unused-disable": "off",
"@eslint-community/eslint-comments/require-description": "error"
};
//#endregion
//#region src/configs/eslint-comments.ts
const eslintCommentsConfig = () => {
return [{
...comments.recommended,
name: "jimmy.codes/eslint-comments",
rules: eslintCommentsRules
}];
};
//#endregion
//#region src/configs/ignores.ts
const ignoresConfig = (ignores) => {
return [{
ignores: [...GLOB_IGNORES, ...ignores],
name: "jimmy.codes/ignores"
}];
};
//#endregion
//#region src/rules/imports.ts
const importsRules = {
...configs.recommended.rules,
"import-x/consistent-type-specifier-style": ["error", "prefer-top-level"],
"import-x/extensions": [
"error",
"never",
{
checkTypedImports: true,
svg: "always"
}
],
"import-x/first": "error",
"import-x/namespace": "off",
"import-x/newline-after-import": "error",
"import-x/no-absolute-path": "error",
"import-x/no-duplicates": "error",
"import-x/no-empty-named-blocks": "error",
"import-x/no-named-as-default": "error",
"import-x/no-named-as-default-member": "error",
"import-x/no-self-import": "error",
"import-x/no-unresolved": ["error", { ignore: [String.raw`\.svg$`] }],
"import-x/no-useless-path-segments": "error"
};
//#endregion
//#region src/configs/imports.ts
const importsTypescriptConfig = () => {
const { rules, settings } = configs.typescript;
return [{
name: "jimmy.codes/imports/typescript",
rules,
settings: {
"import-x/extensions": settings["import-x/extensions"],
"import-x/external-module-folders": settings["import-x/external-module-folders"],
"import-x/parsers": settings["import-x/parsers"],
"import-x/resolver-next": [createTypeScriptImportResolver()]
}
}];
};
const importsConfig = ({ isTypescriptEnabled = false } = {}) => {
return [{
name: "jimmy.codes/imports",
plugins: {
"import-x": importX,
"n": nodePlugin
},
rules: importsRules
}, ...isTypescriptEnabled ? importsTypescriptConfig() : []];
};
//#endregion
//#region src/rules/javascript.ts
const additionalRules = {
"array-callback-return": ["error", { allowImplicit: true }],
"arrow-body-style": ["error", "always"],
"class-methods-use-this": "error",
"consistent-return": "error",
"curly": ["error", "all"],
"default-case": "error",
"default-case-last": "error",
"eqeqeq": "error",
"no-console": "warn",
"no-div-regex": "error",
"no-else-return": ["error", { allowElseIf: false }],
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-magic-numbers": "off",
"no-new-wrappers": "error",
"no-param-reassign": ["error", { props: true }],
"no-promise-executor-return": "error",
"no-self-compare": "error",
"no-template-curly-in-string": "error",
"no-throw-literal": "error",
"no-unassigned-vars": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unreachable-loop": "error",
"no-use-before-define": ["error", {
allowNamedExports: false,
classes: false,
functions: true,
variables: true
}],
"no-useless-computed-key": "error",
"no-useless-constructor": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"object-shorthand": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-destructuring": ["error", {
AssignmentExpression: {
array: false,
object: false
},
VariableDeclarator: {
array: false,
object: true
}
}],
"prefer-object-spread": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"preserve-caught-error": "error",
"radix": "error",
"require-await": "error",
"strict": ["error", "safe"],
"symbol-description": "error"
};
const javascriptRules = {
...eslint.configs.recommended.rules,
...additionalRules
};
//#endregion
//#region src/configs/javascript.ts
const javascriptConfig = () => {
return [{
linterOptions: { reportUnusedDisableDirectives: true },
name: "jimmy.codes/javascript",
rules: javascriptRules
}, {
files: GLOB_TESTS,
name: "jimmy.codes/javascript/testing",
rules: { "no-magic-numbers": "off" }
}];
};
//#endregion
//#region src/rules/jsdoc.ts
const jsdocRules = () => {
return {
...jsdocPlugin.configs["flat/recommended-typescript-error"].rules,
"jsdoc/prefer-import-tag": "error",
"jsdoc/require-jsdoc": "off",
"jsdoc/require-next-description": "error",
"jsdoc/require-param": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-template-description": "error",
"jsdoc/require-throws-description": "error",
"jsdoc/require-yields-description": "error",
"jsdoc/tag-lines": [
"error",
"always",
{
applyToEndTag: false,
startLines: 1
}
]
};
};
//#endregion
//#region src/configs/jsdoc.ts
const jsdocConfig = () => {
return [{
...jsdocPlugin.configs["flat/recommended-typescript-error"],
name: "jimmy.codes/jsdoc",
rules: jsdocRules()
}];
};
//#endregion
//#region src/rules/node.ts
const nodeRules = {
"n/handle-callback-err": ["error", "^(err|error)$"],
"n/no-deprecated-api": "error",
"n/no-exports-assign": "error",
"n/no-new-require": "error",
"n/no-path-concat": "error",
"n/no-process-exit": "off",
"n/no-top-level-await": ["error", { ignoreBin: true }],
"n/prefer-global/console": ["error", "always"],
"n/prefer-node-protocol": "error",
"n/process-exit-as-throw": "error"
};
//#endregion
//#region src/configs/node.ts
const nodeConfig = () => {
return [{
name: "jimmy.codes/node",
plugins: { n: nodePlugin },
rules: nodeRules
}];
};
//#endregion
//#region src/rules/perfectionist.ts
const perfectionistRules = {
...perfectionist.configs["recommended-natural"].rules,
"perfectionist/sort-imports": ["error", {
customGroups: {
type: {},
value: {}
},
environment: "node",
groups: [
"side-effect-style",
"builtin",
"type",
"external",
"internal-type",
"internal",
[
"parent-type",
"sibling-type",
"index-type"
],
[
"parent",
"sibling",
"index"
],
"object",
"style",
"unknown"
],
internalPattern: ["^~/.*", "^@/.*"],
order: "asc",
type: "natural"
}],
"perfectionist/sort-modules": "off"
};
//#endregion
//#region src/configs/perfectionist.ts
const perfectionistConfig = () => {
return [{
name: "jimmy.codes/perfectionist",
plugins: { perfectionist },
rules: perfectionistRules
}];
};
//#endregion
//#region src/configs/prettier.ts
const prettierConfig = () => {
return [{
...eslintConfigPrettier,
name: "jimmy.codes/prettier"
}];
};
//#endregion
//#region src/rules/regexp.ts
const regexpRules = {
...regexpPlugin.configs["flat/recommended"].rules,
"regexp/confusing-quantifier": "error",
"regexp/no-empty-alternative": "error",
"regexp/no-lazy-ends": "error",
"regexp/no-potentially-useless-backreference": "error",
"regexp/no-useless-flag": "error",
"regexp/optimal-lookaround-quantifier": "error"
};
//#endregion
//#region src/configs/regexp.ts
const regexpConfig = () => {
return [{
name: "jimmy.codes/regexp",
plugins: { regexp: regexpPlugin },
rules: regexpRules
}];
};
//#endregion
//#region src/rules/stylistic.ts
const stylisticRules = {
"@stylistic/jsx-curly-brace-presence": ["error", "never"],
"@stylistic/object-curly-newline": ["error", {
consistent: true,
multiline: true
}],
"@stylistic/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }],
"@stylistic/padding-line-between-statements": [
"error",
{
blankLine: "always",
next: "return",
prev: "*"
},
{
blankLine: "always",
next: "*",
prev: [
"const",
"let",
"var"
]
},
{
blankLine: "any",
next: [
"const",
"let",
"var"
],
prev: [
"const",
"let",
"var"
]
},
{
blankLine: "always",
next: "*",
prev: "directive"
},
{
blankLine: "any",
next: "directive",
prev: "directive"
},
{
blankLine: "always",
next: "function",
prev: "*"
}
]
};
//#endregion
//#region src/configs/stylistic.ts
function stylisticConfig() {
return [{
name: "jimmy.codes/stylistic",
plugins: { "@stylistic": stylisticPlugin },
rules: stylisticRules
}];
}
//#endregion
//#region src/rules/unicorn.ts
const unicornRules = {
...eslintPluginUnicorn.configs.recommended.rules,
"unicorn/filename-case": "off",
"unicorn/import-style": "off",
"unicorn/no-abusive-eslint-disable": "off",
"unicorn/no-anonymous-default-export": "off",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-array-reduce": "off",
"unicorn/no-null": "off",
"unicorn/no-process-exit": "off",
"unicorn/no-useless-undefined": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/prevent-abbreviations": "off"
};
//#endregion
//#region src/configs/unicorn.ts
const unicornConfig = () => {
return [{
...eslintPluginUnicorn.configs.recommended,
name: "jimmy.codes/unicorn",
rules: unicornRules
}];
};
//#endregion
//#region src/utils/unwrap.ts
/**
* Unwraps an imported module, resolving its `default` export if it's a function.
*
* This is useful for handling ESLint configurations and other dynamically imported modules,
* ensuring compatibility with both named and default exports.
*
* @template T - The imported module type.
*
* @param module - A dynamically imported module.
*
* @returns
* If the module has a `default` export that is a function, it returns the result of calling it.
* Otherwise, it returns the module itself.
*
* @example
* const config = await unwrap(import("./configs/react"));
*/
const unwrap = async (module) => {
const resolved = await module;
if (typeof resolved.default === "function") return resolved.default();
return resolved;
};
//#endregion
//#region src/factory.ts
/**
* Generates an ESLint configuration based on the provided options.
*
* @returns The resolved ESLint configuration.
*
* @example
* import { defineConfig } from "@jimmy.codes/eslint-config";
*
* export default defineConfig();
*/
const defineConfig = async ({ astro = false, autoDetect = true, gitignore = false, ignores = [], jest = false, nextjs = false, overrides = [], playwright = false, react = false, storybook = false, tanstackQuery = false, testingLibrary = false, typescript = false, vitest = false } = {}, ...moreOverrides) => {
const getFlag = (explicit, detector) => {
return explicit || autoDetect && detector();
};
const isTypescriptEnabled = getFlag(typescript, hasTypescript);
const isReactEnabled = getFlag(react, hasReact);
const isAstroEnabled = getFlag(astro, hasAstro);
const isTanstackQueryEnabled = getFlag(tanstackQuery, hasReactQuery);
const isTestingLibraryEnabled = getFlag(testingLibrary, hasTestingLibrary);
const isPlaywrightEnabled = getFlag(playwright, hasPlaywright);
const isStorybookEnabled = getFlag(storybook, hasStorybook);
const isNextjsEnabled = getFlag(nextjs, hasNext);
const isJestEnabled = getFlag(jest, hasJest);
const isVitestEnabled = getFlag(vitest, hasVitest);
const baseConfigs = [
javascriptConfig(),
perfectionistConfig(),
nodeConfig(),
unicornConfig(),
eslintCommentsConfig(),
regexpConfig(),
jsdocConfig(),
importsConfig({ isTypescriptEnabled }),
stylisticConfig()
];
const featureConfigs = await Promise.all([
isTypescriptEnabled && unwrap(import("./typescript-CWevoym8.js")),
isReactEnabled && unwrap(import("./react-TciwSDvV.js")),
isTanstackQueryEnabled && unwrap(import("./tanstack-query-C0Pag-TK.js")),
isAstroEnabled && unwrap(import("./astro-CYKa1Ijk.js")),
isJestEnabled && unwrap(import("./jest-DBcw_I7S.js")),
isVitestEnabled && unwrap(import("./vitest-Bs_zrIdp.js")),
isTestingLibraryEnabled && unwrap(import("./testing-library-DuiSlHeo.js")),
isPlaywrightEnabled && unwrap(import("./playwright-BXMpcaS7.js")),
isStorybookEnabled && unwrap(import("./storybook-D3Bkozqd.js")),
isNextjsEnabled && unwrap(import("./nextjs-BAZ7sZb7.js"))
]);
return [
...gitignore ? [gitignoreConfig({ strict: false })] : [],
ignoresConfig(ignores),
...baseConfigs,
...featureConfigs.filter(Boolean),
commonjsConfig(),
prettierConfig(),
overrides,
moreOverrides
].flat();
};
//#endregion
export { defineConfig };