@jimmy.codes/eslint-config
Version:
A simple, modern ESLint config that covers most use cases.
588 lines (563 loc) • 15.6 kB
JavaScript
import { i as GLOB_IGNORES, l as GLOB_TESTS, n as GLOB_CJS } from "./globs-uKx5b8lV.mjs";
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-BreXO1rF.mjs";
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 arrowReturnStylePlugin from "eslint-plugin-arrow-return-style-x";
import deMorganPlugin, { configs as configs$1 } from "eslint-plugin-de-morgan";
import eslint from "@eslint/js";
import jsdocPlugin from "eslint-plugin-jsdoc";
import perfectionist, { configs as configs$2 } 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": "off",
"arrow-style/arrow-return-style": ["error", {
jsxAlwaysUseExplicitReturn: true,
namedExportsAlwaysUseExplicitReturn: true,
objectReturnStyle: "off",
usePrettier: false
}],
"arrow-style/no-export-default-arrow": "error",
"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,
...configs$1.recommended.rules
};
//#endregion
//#region src/configs/javascript.ts
const javascriptConfig = () => {
return [{
linterOptions: { reportUnusedDisableDirectives: true },
name: "jimmy.codes/javascript",
plugins: {
"arrow-style": arrowReturnStylePlugin,
"de-morgan": deMorganPlugin
},
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 = {
...configs$2["recommended-natural"].rules,
"perfectionist/sort-imports": ["error", {
environment: "node",
groups: [
"side-effect-style",
"side-effect",
["type-builtin", "type-external"],
"value-builtin",
"value-external",
"type-internal",
"value-internal",
[
"type-parent",
"type-sibling",
"type-index"
],
[
"value-parent",
"value-sibling",
"value-index"
],
"style",
"value-subpath",
"ts-equals-import",
"unknown"
],
internalPattern: ["^~/.*", "^@/.*"],
order: "asc",
type: "natural"
}],
"perfectionist/sort-modules": ["error", {
groups: [
"enum",
["declare-interface", "declare-type"],
"type",
"interface",
"declare-class",
"class",
"declare-function",
"function",
"export-enum",
["export-interface", "export-type"],
"export-class",
"export-function",
"unknown"
],
order: "asc",
partitionByNewLine: true,
type: "natural"
}]
};
//#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: "break",
prev: "*"
},
{
blankLine: "always",
next: "throw",
prev: "*"
},
{
blankLine: "always",
next: "*",
prev: [
"const",
"let",
"var"
]
},
{
blankLine: "any",
next: [
"const",
"let",
"var"
],
prev: [
"const",
"let",
"var"
]
},
{
blankLine: "always",
next: ["let", "var"],
prev: "const"
},
{
blankLine: "always",
next: ["const", "var"],
prev: "let"
},
{
blankLine: "always",
next: ["const", "let"],
prev: "var"
},
{
blankLine: "always",
next: "*",
prev: "directive"
},
{
blankLine: "any",
next: "directive",
prev: "directive"
},
{
blankLine: "always",
next: "function",
prev: "*"
},
{
blankLine: "always",
next: "if",
prev: "if"
},
{
blankLine: "always",
next: "*",
prev: "if"
}
]
};
//#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/create-featured.ts
/**
* Creates a featured flag checker function.
*
* @param autoDetect - Whether to auto-detect features.
*
* @returns A function that determines if a feature is enabled.
*/
const createFeatured = (autoDetect) => {
return (explicit, detector) => {
if (typeof explicit !== "boolean") return true;
return explicit || autoDetect && detector();
};
};
//#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.
*
* @param args - Optional arguments forwarded to the module's default export if it's a function.
*
* @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 configWithOptions = await unwrap(import("./configs/react"), { version: "12" });
*/
const unwrap = async (module, ...args) => {
const resolved = await module;
if (typeof resolved.default === "function") return resolved.default(...args);
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 featured = createFeatured(autoDetect);
const isTypescriptEnabled = featured(typescript, hasTypescript);
const isReactEnabled = featured(react, hasReact);
const isAstroEnabled = featured(astro, hasAstro);
const isTanstackQueryEnabled = featured(tanstackQuery, hasReactQuery);
const isTestingLibraryEnabled = featured(testingLibrary, hasTestingLibrary);
const isPlaywrightEnabled = featured(playwright, hasPlaywright);
const isStorybookEnabled = featured(storybook, hasStorybook);
const isNextjsEnabled = featured(nextjs, hasNext);
const isJestEnabled = featured(jest, hasJest);
const isVitestEnabled = featured(vitest, hasVitest);
const baseConfigs = [
javascriptConfig(),
perfectionistConfig(),
nodeConfig(),
unicornConfig(),
eslintCommentsConfig(),
regexpConfig(),
jsdocConfig(),
importsConfig({ isTypescriptEnabled }),
stylisticConfig()
];
const featureConfigs = await Promise.all([
isTypescriptEnabled && unwrap(import("./typescript-CNtyo5Lg.mjs"), typescript),
isReactEnabled && unwrap(import("./react-6Ule3BqW.mjs"), react),
isTanstackQueryEnabled && unwrap(import("./tanstack-query-BVXIAVcv.mjs"), tanstackQuery),
isAstroEnabled && unwrap(import("./astro-BnZ2LIR3.mjs"), astro),
isJestEnabled && unwrap(import("./jest-C3ee1wyj.mjs"), jest),
isVitestEnabled && unwrap(import("./vitest-BkzQGlIC.mjs"), vitest),
isTestingLibraryEnabled && unwrap(import("./testing-library-BFsBlKXl.mjs"), testingLibrary),
isPlaywrightEnabled && unwrap(import("./playwright-ChDf6HhZ.mjs"), playwright),
isStorybookEnabled && unwrap(import("./storybook-BfbeHUhi.mjs"), storybook),
isNextjsEnabled && unwrap(import("./nextjs-_AmGd366.mjs"), nextjs)
]);
return [
...gitignore ? [gitignoreConfig({ strict: false })] : [],
ignoresConfig(ignores),
...baseConfigs,
...featureConfigs.filter(Boolean),
commonjsConfig(),
prettierConfig(),
overrides,
moreOverrides
].flat();
};
//#endregion
export { defineConfig };