@jimmy.codes/eslint-config
Version:
A simple, modern ESLint config that covers most use cases.
609 lines (577 loc) • 17.2 kB
JavaScript
import {
hasAstro,
hasJest,
hasNext,
hasPlaywright,
hasReact,
hasReactQuery,
hasStorybook,
hasTestingLibrary,
hasTypescript,
hasVitest
} from "./chunk-OCS4JNPP.js";
import {
GLOB_CJS,
GLOB_IGNORES,
GLOB_TESTS
} from "./chunk-N5KZEOXT.js";
// src/configs/commonjs.ts
import globals from "globals";
var commonjsConfig = () => {
return [
{
files: [GLOB_CJS],
languageOptions: {
globals: globals.commonjs
},
name: "jimmy.codes/commonjs"
}
];
};
// src/configs/eslint-comments.ts
import comments2 from "@eslint-community/eslint-plugin-eslint-comments/configs";
// src/rules/eslint-comments.ts
import comments from "@eslint-community/eslint-plugin-eslint-comments/configs";
var eslintCommentsRules = {
...comments.recommended.rules,
"@eslint-community/eslint-comments/no-unused-disable": "off",
"@eslint-community/eslint-comments/require-description": "error"
};
// src/configs/eslint-comments.ts
var eslintCommentsConfig = () => {
return [
{
...comments2.recommended,
name: "jimmy.codes/eslint-comments",
rules: eslintCommentsRules
}
];
};
// src/configs/ignores.ts
var ignoresConfig = (ignores) => {
return [
{
ignores: [...GLOB_IGNORES, ...ignores],
name: "jimmy.codes/ignores"
}
];
};
// src/configs/imports.ts
import { createTypeScriptImportResolver } from "eslint-import-resolver-typescript";
import { configs as configs2, importX } from "eslint-plugin-import-x";
import nodePlugin from "eslint-plugin-n";
// src/rules/imports.ts
import { configs } from "eslint-plugin-import-x";
var 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",
// ! can't get this rule to work
"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"
};
// src/configs/imports.ts
var importsTypescriptConfig = () => {
const { rules, settings } = configs2.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()]
}
}
];
};
var importsConfig = ({
isTypescriptEnabled = false
} = {}) => {
return [
{
name: "jimmy.codes/imports",
plugins: {
"import-x": importX,
"n": nodePlugin
},
rules: importsRules
},
...isTypescriptEnabled ? importsTypescriptConfig() : []
];
};
// src/rules/javascript.ts
import eslint from "@eslint/js";
var additionalRules = {
"array-callback-return": [
"error",
{
allowImplicit: true
// Allow implicit return for flexibility while enforcing return consistency
}
],
"arrow-body-style": ["error", "always"],
// Enforce `{}` in arrow functions for consistency (open to revisiting)
"class-methods-use-this": "error",
// Ensures class methods are used within their class (avoids unnecessary methods)
"consistent-return": "error",
// Prevents inconsistent function returns (e.g., sometimes returning `undefined`)
"curly": ["error", "all"],
// Enforce `{}` around all control statements (avoids accidental bugs)
"default-case": "error",
// Require `default` case in switch statements (prevents unintended fallthrough)
"default-case-last": "error",
// Ensure the `default` case is the last one in `switch`
"no-console": "warn",
// Allow logging in development, but should be reviewed for production builds
"no-implicit-coercion": "error",
// Prevents shorthand type conversions (e.g., `!!value`, `+var`)
"no-implicit-globals": "error",
// Prevents defining variables in the global scope
"no-loop-func": "error",
// Disallow function declarations inside loops (prevents scoping issues)
"no-magic-numbers": ["error", { ignore: [0, 1, -1, 2] }],
// Helps catch unexplained numbers while allowing common values
"no-new-wrappers": "error",
// Prevents `new String()`, `new Number()`, etc. (use literals instead)
"no-param-reassign": ["error", { props: true }],
// Disallow reassigning function parameters but allow modifying object properties
"no-promise-executor-return": "error",
// Ensures `new Promise` executors always return something
"no-self-compare": "error",
// Disallows `x === x` (usually a mistake)
"no-template-curly-in-string": "error",
// Flags unintended use of `${}` inside strings without backticks
"no-throw-literal": "error",
// Prevents throwing non-error objects (e.g., `throw "error"` should be `throw new Error("error")`)
"no-unassigned-vars": "error",
// Disallow let or var variables that are read but never assigned
"no-unmodified-loop-condition": "error",
// Ensures loop conditions change, preventing infinite loops
"no-unreachable-loop": "error",
// Prevents loops that can never execute
"no-use-before-define": [
"error",
{
allowNamedExports: false,
classes: false,
functions: true,
// Allow function hoisting
variables: true
}
],
"no-useless-computed-key": "error",
// Disallows unnecessary computed keys in objects (e.g., `{["key"]: value}` instead of `{ key: value }`)
"no-useless-constructor": "error",
// Disallows empty constructors
"no-useless-rename": "error",
// Disallows renaming variables to the same name in import/export/destructuring
"no-useless-return": "error",
// Prevents redundant `return;` statements
"no-var": "error",
// Enforce `let` and `const` (modern best practice)
"object-shorthand": "error",
// Enforces `{ foo }` instead of `{ foo: foo }`
"prefer-arrow-callback": "error",
// Enforces arrow functions in callbacks where possible
"prefer-const": "error",
// Requires `const` when a variable isn’t reassigned
"prefer-destructuring": [
"error",
{
AssignmentExpression: {
array: false,
// Allow `x = arr[0]`, don't force `[x] = arr`
object: false
// Allow `x = obj.x`, don't force `{ x } = obj`
},
VariableDeclarator: {
array: false,
object: true
// Enforce destructuring for objects (`const { x } = obj`)
}
}
],
"prefer-object-spread": "error",
// Enforces `{ ...obj }` over `Object.assign()`
"prefer-rest-params": "error",
// Use `...args` instead of `arguments`
"prefer-spread": "error",
// Use `[...arr]` instead of `arr.concat()`
"prefer-template": "error",
// Use template literals instead of string concatenation
"radix": "error",
// Require a radix argument in `parseInt`
"require-await": "error",
// Disallows async functions without `await` (prevents unnecessary `async`)
"strict": ["error", "safe"],
// Enforces `"use strict"` only where needed (safe mode)
"symbol-description": "error"
// Requires descriptions when creating `Symbol()`
};
var javascriptRules = {
...eslint.configs.recommended.rules,
...additionalRules
};
// src/configs/javascript.ts
var javascriptConfig = () => {
return [
{
linterOptions: {
reportUnusedDisableDirectives: true
},
name: "jimmy.codes/javascript",
rules: javascriptRules
},
{
files: GLOB_TESTS,
name: "jimmy.codes/javascript/testing",
rules: {
"no-magic-numbers": "off"
}
}
];
};
// src/configs/jsdoc.ts
import jsdocPlugin2 from "eslint-plugin-jsdoc";
// src/rules/jsdoc.ts
import jsdocPlugin from "eslint-plugin-jsdoc";
var jsdocRules = () => {
return {
...jsdocPlugin.configs["flat/recommended-typescript-error"].rules,
"jsdoc/require-jsdoc": "off",
// Disable enforced JSDoc (TypeScript provides type info)
"jsdoc/require-param": "off",
// Avoid redundant param documentation (rely on TypeScript)
"jsdoc/require-returns": "off",
// Avoid redundant return type documentation (rely on TypeScript)
"jsdoc/tag-lines": [
"error",
"always",
{
applyToEndTag: false,
// No need for a blank line before closing JSDoc
startLines: 1
// Require exactly one blank line before the first tag
}
]
};
};
// src/configs/jsdoc.ts
var jsdocConfig = () => {
return [
{
...jsdocPlugin2.configs["flat/recommended-typescript-error"],
name: "jimmy.codes/jsdoc",
rules: jsdocRules()
}
];
};
// src/configs/node.ts
import nodePlugin2 from "eslint-plugin-n";
// src/rules/node.ts
var 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",
// TODO [2025-12-31]: enable this rule
"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"
};
// src/configs/node.ts
var nodeConfig = () => {
return [
{
name: "jimmy.codes/node",
plugins: {
n: nodePlugin2
},
rules: nodeRules
}
];
};
// src/configs/perfectionist.ts
import perfectionist2 from "eslint-plugin-perfectionist";
// src/rules/perfectionist.ts
import perfectionist from "eslint-plugin-perfectionist";
var 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"
}
],
// TODO: enable perfectionist/sort-modules
// "perfectionist/sort-modules": [
// "error",
// {
// customGroups: [],
// groups: [
// "declare-enum",
// "enum",
// "export-enum",
// ["declare-interface", "declare-type"],
// ["interface", "type"],
// ["export-interface", "export-type"],
// "declare-class",
// "class",
// "export-class",
// "declare-function",
// "function",
// "export-function",
// ],
// ignoreCase: true,
// newlinesBetween: "ignore",
// order: "asc",
// partitionByComment: false,
// partitionByNewLine: false,
// specialCharacters: "keep",
// type: "natural",
// },
// ],
"perfectionist/sort-modules": "off"
};
// src/configs/perfectionist.ts
var perfectionistConfig = () => {
return [
{
name: "jimmy.codes/perfectionist",
plugins: {
// TODO: remove unknown conversion
perfectionist: perfectionist2
},
rules: perfectionistRules
}
];
};
// src/configs/prettier.ts
import eslintConfigPrettier from "eslint-config-prettier/flat";
var prettierConfig = () => {
return [
{ ...eslintConfigPrettier, name: "jimmy.codes/prettier" }
];
};
// src/configs/regexp.ts
import * as regexpPlugin2 from "eslint-plugin-regexp";
// src/rules/regexp.ts
import * as regexpPlugin from "eslint-plugin-regexp";
var 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"
};
// src/configs/regexp.ts
var regexpConfig = () => {
return [
{
name: "jimmy.codes/regexp",
plugins: { regexp: regexpPlugin2 },
rules: regexpRules
}
];
};
// src/configs/stylistic.ts
import stylisticPlugin from "@stylistic/eslint-plugin";
// src/rules/stylistic.ts
var 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: "*"
}
]
};
// src/configs/stylistic.ts
function stylisticConfig() {
return [
{
name: "jimmy.codes/stylistic",
plugins: {
"@stylistic": stylisticPlugin
},
rules: stylisticRules
}
];
}
// src/configs/unicorn.ts
import eslintPluginUnicorn2 from "eslint-plugin-unicorn";
// src/rules/unicorn.ts
import eslintPluginUnicorn from "eslint-plugin-unicorn";
var 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",
// TODO: enable when https://github.com/sindresorhus/eslint-plugin-unicorn/issues/781 is resolved.
"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"
};
// src/configs/unicorn.ts
var unicornConfig = () => {
return [
{
...eslintPluginUnicorn2.configs.recommended,
name: "jimmy.codes/unicorn",
rules: unicornRules
}
];
};
// src/utils/unwrap.ts
var unwrap = async (module) => {
const resolved = await module;
if (typeof resolved.default === "function") {
return resolved.default();
}
return resolved;
};
// src/factory.ts
var defineConfig = async ({
astro = false,
autoDetect = true,
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-M6XXI2FU.js")),
isReactEnabled && unwrap(import("./react-6ERTZG2I.js")),
isTanstackQueryEnabled && unwrap(import("./tanstack-query-QBZ3MKQ7.js")),
isAstroEnabled && unwrap(import("./astro-Z5RFF624.js")),
isJestEnabled && unwrap(import("./jest-7NR7TDOP.js")),
isVitestEnabled && unwrap(import("./vitest-726PXOPS.js")),
isTestingLibraryEnabled && unwrap(import("./testing-library-HML5O6UC.js")),
isPlaywrightEnabled && unwrap(import("./playwright-BIJ7HLQZ.js")),
isStorybookEnabled && unwrap(import("./storybook-XHFO7L4T.js")),
isNextjsEnabled && unwrap(import("./nextjs-7V464KOE.js"))
]);
return [
...baseConfigs,
...featureConfigs.filter(Boolean),
commonjsConfig(),
ignoresConfig(ignores),
prettierConfig(),
overrides,
moreOverrides
].flat();
};
export {
defineConfig
};