UNPKG

@jimmy.codes/eslint-config

Version:

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

588 lines (563 loc) 15.6 kB
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 };