UNPKG

@jimmy.codes/eslint-config

Version:

A simple, modern ESLint config that covers most use cases.

505 lines (481 loc) 13.5 kB
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 };