UNPKG

@coderwyd/eslint-config

Version:
1,693 lines (1,666 loc) 51.8 kB
import process from "node:process"; import createCommand from "eslint-plugin-command/config"; import pluginComments from "@eslint-community/eslint-plugin-eslint-comments"; import pluginAntfu from "eslint-plugin-antfu"; import pluginDeMorgan from "eslint-plugin-de-morgan"; import pluginNode from "eslint-plugin-n"; import pluginPerfectionist from "eslint-plugin-perfectionist"; import pluginUnicorn from "eslint-plugin-unicorn"; import pluginUnusedImports from "eslint-plugin-unused-imports"; import { fileURLToPath } from "node:url"; import { getPackageInfoSync, isPackageExists } from "local-pkg"; import globals from "globals"; import prettierRules from "eslint-config-prettier"; import { configs } from "eslint-plugin-regexp"; //#region src/configs/command.ts function command() { return [{ ...createCommand(), name: "coderwyd/command/rules" }]; } //#endregion //#region src/configs/comments.ts function comments() { return [{ name: "coderwyd/eslint-comments/rules", plugins: { "eslint-comments": pluginComments }, rules: { "eslint-comments/no-aggregating-enable": "error", "eslint-comments/no-duplicate-disable": "error", "eslint-comments/no-unlimited-disable": "error", "eslint-comments/no-unused-enable": "error" } }]; } //#endregion //#region src/configs/de-morgan.ts function deMorgan() { return [{ name: "coderwyd/de-morgan/rules", plugins: { "de-morgan": pluginDeMorgan }, rules: { "de-morgan/no-negated-conjunction": "error", "de-morgan/no-negated-disjunction": "error" } }]; } //#endregion //#region src/constants/glob.ts const GLOB_SRC_EXT = "?([cm])[jt]s?(x)"; const GLOB_SRC = "**/*.?([cm])[jt]s?(x)"; const GLOB_TS = "**/*.?([cm])ts"; const GLOB_DTS = "**/*.d.?([cm])ts"; const GLOB_TSX = "**/*.?([cm])tsx"; const GLOB_VUE = "**/*.vue"; const GLOB_ASTRO_TS = "**/*.astro/*.ts"; const GLOB_SVELTE = "**/*.svelte"; const GLOB_JSON = "**/*.json"; const GLOB_JSON5 = "**/*.json5"; const GLOB_JSONC = "**/*.jsonc"; const GLOB_MARKDOWN = "**/*.md"; const GLOB_YAML = "**/*.y?(a)ml"; const GLOB_MARKDOWN_CODE = `${GLOB_MARKDOWN}/${GLOB_SRC}`; const GLOB_TESTS = [ `**/__tests__/**/*.${GLOB_SRC_EXT}`, `**/*.spec.${GLOB_SRC_EXT}`, `**/*.test.${GLOB_SRC_EXT}`, `**/*.bench.${GLOB_SRC_EXT}`, `**/*.benchmark.${GLOB_SRC_EXT}` ]; const GLOB_EXCLUDE = [ "**/node_modules", "**/dist", "**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml", "**/bun.lockb", "**/output", "**/coverage", "**/temp", "**/.temp", "**/tmp", "**/.tmp", "**/.history", "**/.vitepress/cache", "**/.nuxt", "**/.next", "**/.svelte-kit", "**/.vercel", "**/.changeset", "**/.idea", "**/.cache", "**/.output", "**/.vite-inspect", "**/.yarn", "**/vite.config.*.timestamp-*", "**/CHANGELOG*.md", "**/*.min.*", "**/LICENSE*", "**/__snapshots__", "**/auto-import?(s).d.ts", "**/components.d.ts" ]; //#endregion //#region src/configs/ignores.ts function ignores(userIgnores = []) { return [{ ignores: [...GLOB_EXCLUDE, ...userIgnores], name: "coderwyd/ignores" }]; } //#endregion //#region src/shared/index.ts const scopeUrl = fileURLToPath(new URL(".", import.meta.url)); const isCwdInScope = isPackageExists("@coderwyd/eslint-config"); /** * Combine array and non-array configs into a single array. */ async function combine(...configs$1) { const resolved = await Promise.all(configs$1); return resolved.flat(); } /** * Rename plugin prefixes in a rule object. * Accepts a map of prefixes to rename. * * @example * ```ts * import { renameRules } from '@coderwyd/eslint-config' * * export default [{ * rules: renameRules( * { * '@typescript-eslint/indent': 'error' * }, * { '@typescript-eslint': 'ts' } * ) * }] * ``` */ function renameRules(rules, map) { return Object.fromEntries(Object.entries(rules).map(([key, value]) => { for (const [from, to] of Object.entries(map)) if (key.startsWith(`${from}/`)) return [to + key.slice(from.length), value]; return [key, value]; })); } /** * Rename plugin names a flat configs array * * @example * ```ts * import { renamePluginInConfigs } from '@antfu/eslint-config' * import someConfigs from './some-configs' * * export default renamePluginInConfigs(someConfigs, { * '@typescript-eslint': 'ts', * 'import-x': 'import', * }) * ``` */ function renamePluginInConfigs(configs$1, map) { return configs$1.map((i) => { const clone = { ...i }; if (clone.rules) clone.rules = renameRules(clone.rules, map); if (clone.plugins) clone.plugins = Object.fromEntries(Object.entries(clone.plugins).map(([key, value]) => { if (key in map) return [map[key], value]; return [key, value]; })); return clone; }); } function getVueVersion() { const pkg = getPackageInfoSync("vue", { paths: [process.cwd()] }); if (pkg && typeof pkg.version === "string" && !Number.isNaN(+pkg.version[0])) return +pkg.version[0]; return 3; } function toArray(value) { return Array.isArray(value) ? value : [value]; } async function interopDefault(m) { const resolved = await m; return resolved.default || resolved; } function isPackageInScope(name) { return isPackageExists(name, { paths: [scopeUrl] }); } async function ensurePackages(packages) { if (process.env.CI || process.stdout.isTTY === false || isCwdInScope === false) return; const nonExistingPackages = packages.filter((i) => !isPackageInScope(i)); if (nonExistingPackages.length === 0) return; const { default: prompts } = await import("prompts"); const { result } = await prompts([{ message: `${nonExistingPackages.length === 1 ? "Package is" : "Packages are"} required for this config: ${nonExistingPackages.join(", ")}. Do you want to install them?`, name: "result", type: "confirm" }]); if (result) await import("@antfu/install-pkg").then((i) => i.installPackage(nonExistingPackages, { dev: true })); } function resolveSubOptions(options, key) { return typeof options[key] === "boolean" ? {} : options[key] || {}; } function getOverrides(options, key) { const subOptions = resolveSubOptions(options, key); return { ..."overrides" in subOptions && subOptions.overrides ? subOptions.overrides : {} }; } function isInEditorEnv() { if (process.env.CI) return false; if (isInGitHooksOrLintStaged()) return false; return !!(process.env.VSCODE_PID || process.env.VSCODE_CWD || process.env.JETBRAINS_IDE || process.env.VIM || process.env.NVIM || false); } function isInGitHooksOrLintStaged() { return !!(process.env.GIT_PARAMS || process.env.VSCODE_GIT_COMMAND || process.env.npm_lifecycle_script?.startsWith("lint-staged") || process.env.npm_lifecycle_script?.startsWith("nano-staged") || false); } //#endregion //#region src/configs/imports.ts async function imports() { const pluginImport = await interopDefault(import("./dist-DYrxmIwK.js")); return [{ name: "coderwyd/imports/rules", plugins: { antfu: pluginAntfu, import: pluginImport }, rules: { "antfu/import-dedupe": "error", "antfu/no-import-dist": "error", "antfu/no-import-node-modules-by-path": "error", "import/consistent-type-specifier-style": ["error", "prefer-top-level"], "import/first": "error", "import/newline-after-import": ["error", { count: 1 }], "import/no-duplicates": "error", "import/no-mutable-exports": "error", "import/no-named-default": "error" } }]; } //#endregion //#region src/configs/javascript.ts function javascript(options = {}) { const { isInEditor: isInEditor$1 = false, overrides = {} } = options; return [{ languageOptions: { ecmaVersion: 2022, globals: { ...globals.browser, ...globals.es2021, ...globals.node, document: "readonly", navigator: "readonly", window: "readonly" }, parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 2022, sourceType: "module" }, sourceType: "module" }, linterOptions: { reportUnusedDisableDirectives: true }, name: "coderwyd/javascript/setup" }, { name: "coderwyd/javascript/rules", plugins: { "unused-imports": pluginUnusedImports }, rules: { "accessor-pairs": ["error", { enforceForClassMembers: true, setWithoutGet: true }], "antfu/no-top-level-await": "error", "antfu/top-level-function": "error", "array-callback-return": "error", "block-scoped-var": "error", "constructor-super": "error", "default-case-last": "error", "dot-notation": ["error", { allowKeywords: true }], eqeqeq: ["error", "smart"], "for-direction": "error", "getter-return": "error", "new-cap": ["error", { capIsNew: false, newIsCap: true, properties: true }], "no-alert": "error", "no-array-constructor": "error", "no-async-promise-executor": "error", "no-caller": "error", "no-case-declarations": "error", "no-class-assign": "error", "no-compare-neg-zero": "error", "no-cond-assign": ["error", "always"], "no-console": ["error", { allow: ["warn", "error"] }], "no-const-assign": "error", "no-constant-binary-expression": "error", "no-constant-condition": "error", "no-control-regex": "error", "no-debugger": "error", "no-delete-var": "error", "no-dupe-args": "error", "no-dupe-class-members": "error", "no-dupe-keys": "error", "no-duplicate-case": "error", "no-empty": ["error", { allowEmptyCatch: true }], "no-empty-character-class": "error", "no-empty-pattern": "error", "no-eval": "error", "no-ex-assign": "error", "no-extend-native": "error", "no-extra-bind": "error", "no-extra-boolean-cast": "error", "no-fallthrough": "error", "no-func-assign": "error", "no-global-assign": "error", "no-implied-eval": "error", "no-import-assign": "error", "no-invalid-regexp": "error", "no-irregular-whitespace": "error", "no-iterator": "error", "no-labels": ["error", { allowLoop: false, allowSwitch: false }], "no-lone-blocks": "error", "no-loss-of-precision": "error", "no-misleading-character-class": "error", "no-multi-str": "error", "no-new": "error", "no-new-func": "error", "no-new-native-nonconstructor": "error", "no-new-wrappers": "error", "no-obj-calls": "error", "no-object-constructor": "error", "no-octal": "error", "no-octal-escape": "error", "no-proto": "error", "no-prototype-builtins": "error", "no-redeclare": ["error", { builtinGlobals: false }], "no-regex-spaces": "error", "no-restricted-globals": [ "error", { message: "Use `globalThis` instead.", name: "global" }, { message: "Use `globalThis` instead.", name: "self" } ], "no-restricted-properties": [ "error", { message: "Use `Object.getPrototypeOf` or `Object.setPrototypeOf` instead.", property: "__proto__" }, { message: "Use `Object.defineProperty` instead.", property: "__defineGetter__" }, { message: "Use `Object.defineProperty` instead.", property: "__defineSetter__" }, { message: "Use `Object.getOwnPropertyDescriptor` instead.", property: "__lookupGetter__" }, { message: "Use `Object.getOwnPropertyDescriptor` instead.", property: "__lookupSetter__" } ], "no-restricted-syntax": [ "error", "TSEnumDeclaration[const=true]", "TSExportAssignment" ], "no-self-assign": ["error", { props: true }], "no-self-compare": "error", "no-sequences": "error", "no-shadow-restricted-names": "error", "no-sparse-arrays": "error", "no-template-curly-in-string": "error", "no-this-before-super": "error", "no-throw-literal": "error", "no-undef": "error", "no-undef-init": "error", "no-unexpected-multiline": "error", "no-unmodified-loop-condition": "error", "no-unneeded-ternary": ["error", { defaultAssignment: false }], "no-unreachable": "error", "no-unreachable-loop": "error", "no-unsafe-finally": "error", "no-unsafe-negation": "error", "no-unused-expressions": ["error", { allowShortCircuit: true, allowTaggedTemplates: true, allowTernary: true }], "no-unused-vars": "off", "no-use-before-define": ["error", { classes: false, functions: false, variables: true }], "no-useless-backreference": "error", "no-useless-call": "error", "no-useless-catch": "error", "no-useless-computed-key": "error", "no-useless-constructor": "error", "no-useless-rename": "error", "no-useless-return": "error", "no-var": "error", "no-with": "error", "object-shorthand": [ "error", "always", { avoidQuotes: true, ignoreConstructors: false } ], "one-var": ["error", { initialized: "never" }], "prefer-arrow-callback": ["error", { allowNamedFunctions: false, allowUnboundThis: true }], "prefer-const": ["error", { destructuring: "all", ignoreReadBeforeAssign: true }], "prefer-exponentiation-operator": "error", "prefer-regex-literals": ["error", { disallowRedundantWrapping: true }], "prefer-rest-params": "error", "prefer-spread": "error", "prefer-template": "error", "require-await": "error", "require-yield": "error", "symbol-description": "error", "unicode-bom": ["error", "never"], "unused-imports/no-unused-imports": isInEditor$1 ? "off" : "error", "unused-imports/no-unused-vars": ["error", { args: "after-used", argsIgnorePattern: "^_", ignoreRestSiblings: true, vars: "all", varsIgnorePattern: "^_" }], "use-isnan": ["error", { enforceForIndexOf: true, enforceForSwitchCase: true }], "valid-typeof": ["error", { requireStringLiterals: true }], "vars-on-top": "error", yoda: ["error", "never"], ...overrides } }]; } //#endregion //#region src/configs/jsdoc.ts async function jsdoc() { return [{ name: "coderwyd/jsdoc/rules", plugins: { jsdoc: await interopDefault(import("eslint-plugin-jsdoc")) }, rules: { "jsdoc/check-access": "warn", "jsdoc/check-param-names": "warn", "jsdoc/check-property-names": "warn", "jsdoc/check-types": "warn", "jsdoc/empty-tags": "warn", "jsdoc/implements-on-classes": "warn", "jsdoc/no-defaults": "warn", "jsdoc/no-multi-asterisks": "warn", "jsdoc/require-param-name": "warn", "jsdoc/require-property": "warn", "jsdoc/require-property-description": "warn", "jsdoc/require-property-name": "warn", "jsdoc/require-returns-check": "warn", "jsdoc/require-returns-description": "warn", "jsdoc/require-yields-check": "warn" } }]; } //#endregion //#region src/configs/jsonc.ts async function jsonc(options = {}) { const { files = [ GLOB_JSON, GLOB_JSON5, GLOB_JSONC ], overrides = {} } = options || {}; const [pluginJsonc, parserJsonc] = await Promise.all([interopDefault(import("eslint-plugin-jsonc")), interopDefault(import("jsonc-eslint-parser"))]); return [{ name: "coderwyd/jsonc/setup", plugins: { jsonc: pluginJsonc } }, { files, languageOptions: { parser: parserJsonc }, name: "coderwyd/jsonc/rules", rules: { "jsonc/no-bigint-literals": "error", "jsonc/no-binary-expression": "error", "jsonc/no-binary-numeric-literals": "error", "jsonc/no-dupe-keys": "error", "jsonc/no-escape-sequence-in-identifier": "error", "jsonc/no-floating-decimal": "error", "jsonc/no-hexadecimal-numeric-literals": "error", "jsonc/no-infinity": "error", "jsonc/no-multi-str": "error", "jsonc/no-nan": "error", "jsonc/no-number-props": "error", "jsonc/no-numeric-separators": "error", "jsonc/no-octal": "error", "jsonc/no-octal-escape": "error", "jsonc/no-octal-numeric-literals": "error", "jsonc/no-parenthesized": "error", "jsonc/no-plus-sign": "error", "jsonc/no-regexp-literals": "error", "jsonc/no-sparse-arrays": "error", "jsonc/no-template-literals": "error", "jsonc/no-undefined-value": "error", "jsonc/no-unicode-codepoint-escapes": "error", "jsonc/no-useless-escape": "error", "jsonc/space-unary-ops": "error", "jsonc/valid-json-number": "error", "jsonc/vue-custom-block/no-parsing-error": "error", ...overrides } }]; } //#endregion //#region src/configs/node.ts function node() { return [{ name: "coderwyd/node/rules", plugins: { node: pluginNode }, rules: { "node/handle-callback-err": ["error", "^(err|error)$"], "node/no-deprecated-api": "error", "node/no-exports-assign": "error", "node/no-new-require": "error", "node/no-path-concat": "error", "node/no-unsupported-features/es-builtins": "error", "node/prefer-global/buffer": ["error", "never"], "node/prefer-global/process": ["error", "never"], "node/process-exit-as-throw": "error" } }]; } //#endregion //#region src/configs/perfectionist.ts /** * Optional perfectionist plugin for props and items sorting. * * @see https://github.com/azat-io/eslint-plugin-perfectionist */ function perfectionist() { return [{ name: "coderwyd/perfectionist/rules", plugins: { perfectionist: pluginPerfectionist }, rules: { "perfectionist/sort-exports": ["error", { order: "asc", type: "natural" }], "perfectionist/sort-imports": ["warn", { groups: [ "side-effect-style", "value-style", "value-builtin", "value-external", "value-subpath", "value-internal", "value-parent", "value-sibling", "value-index", "ts-equals-import", "side-effect", "type-builtin", "type-external", "type-subpath", "type-internal", "type-parent", "type-sibling", "type-index", "unknown" ], internalPattern: ["^[~@#]/.*"], newlinesBetween: "ignore", order: "asc", type: "natural" }], "perfectionist/sort-named-exports": ["warn", { groupKind: "values-first", order: "asc", type: "natural" }], "perfectionist/sort-named-imports": ["warn", { groupKind: "values-first", order: "asc", type: "natural" }] } }]; } //#endregion //#region src/configs/prettier.ts const { rules: eslintRules } = prettierRules; function prettier() { return [{ name: "coderwyd/prettier/rules", rules: { ...eslintRules, "arrow-body-style": "off", "prefer-arrow-callback": "off" } }]; } //#endregion //#region src/env.ts const isInEditor = !!((process.env.VSCODE_PID || process.env.VSCODE_CWD || process.env.JETBRAINS_IDE || process.env.VIM || process.env.NVIM) && !process.env.CI); const VueJsPackages = [ "vue", "nuxt", "vitepress", "@slidev/cli" ]; const RemixPackages = [ "@remix-run/node", "@remix-run/react", "@remix-run/serve", "@remix-run/dev" ]; const ReactRouterPackages = [ "@react-router/node", "@react-router/react", "@react-router/serve", "@react-router/dev" ]; const NextJsPackages = ["next"]; const ReactRefreshAllowConstantExportPackages = ["vite"]; const isUsingTypeScript = isPackageExists("typescript"); const isUsingVue = hasPackages(VueJsPackages); const isUsingRemix = hasPackages(RemixPackages); const isUsingReactRouter = hasPackages(ReactRouterPackages); const isUsingNext = hasPackages(NextJsPackages); const isAllowConstantExport = hasPackages(ReactRefreshAllowConstantExportPackages); function hasPackages(packages) { return packages.some((name) => isPackageExists(name)); } //#endregion //#region src/configs/react.ts async function react(options = {}) { const { files = [GLOB_SRC], filesTypeAware = [GLOB_TS, GLOB_TSX], ignoresTypeAware = [`${GLOB_MARKDOWN}/**`, GLOB_ASTRO_TS], overrides = {}, tsconfigPath } = options; await ensurePackages([ "@eslint-react/eslint-plugin", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh" ]); const isTypeAware = !!tsconfigPath; const typeAwareRules = { "react/no-leaked-conditional-rendering": "warn" }; const [pluginReact, pluginReactHooks, pluginReactRefresh] = await Promise.all([ interopDefault(import("@eslint-react/eslint-plugin")), interopDefault(import("eslint-plugin-react-hooks")), interopDefault(import("eslint-plugin-react-refresh")) ]); const plugins = pluginReact.configs.all.plugins; return [ { name: "coderwyd/react/setup", plugins: { react: plugins["@eslint-react"], "react-dom": plugins["@eslint-react/dom"], "react-hooks": pluginReactHooks, "react-hooks-extra": plugins["@eslint-react/hooks-extra"], "react-naming-convention": plugins["@eslint-react/naming-convention"], "react-refresh": pluginReactRefresh, "react-web-api": plugins["@eslint-react/web-api"] } }, { files, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } }, sourceType: "module" }, name: "coderwyd/react/rules", rules: { "react/no-access-state-in-setstate": "error", "react/no-array-index-key": "warn", "react/no-children-count": "warn", "react/no-children-for-each": "warn", "react/no-children-map": "warn", "react/no-children-only": "warn", "react/no-children-to-array": "warn", "react/no-clone-element": "warn", "react/no-comment-textnodes": "warn", "react/no-component-will-mount": "error", "react/no-component-will-receive-props": "error", "react/no-component-will-update": "error", "react/no-context-provider": "warn", "react/no-create-ref": "error", "react/no-default-props": "error", "react/no-direct-mutation-state": "error", "react/no-duplicate-jsx-props": "warn", "react/no-duplicate-key": "warn", "react/no-forward-ref": "warn", "react/no-implicit-key": "warn", "react/no-missing-key": "error", "react/no-nested-component-definitions": "error", "react/no-prop-types": "error", "react/no-redundant-should-component-update": "error", "react/no-set-state-in-component-did-mount": "warn", "react/no-set-state-in-component-did-update": "warn", "react/no-set-state-in-component-will-update": "warn", "react/no-string-refs": "error", "react/no-unsafe-component-will-mount": "warn", "react/no-unsafe-component-will-receive-props": "warn", "react/no-unsafe-component-will-update": "warn", "react/no-unstable-context-value": "warn", "react/no-unstable-default-props": "warn", "react/no-unused-class-component-members": "warn", "react/no-unused-state": "warn", "react/no-use-context": "warn", "react/no-useless-forward-ref": "warn", "react/use-jsx-vars": "warn", "react-dom/no-dangerously-set-innerhtml": "warn", "react-dom/no-dangerously-set-innerhtml-with-children": "error", "react-dom/no-find-dom-node": "error", "react-dom/no-flush-sync": "error", "react-dom/no-hydrate": "error", "react-dom/no-missing-button-type": "warn", "react-dom/no-missing-iframe-sandbox": "warn", "react-dom/no-namespace": "error", "react-dom/no-render": "error", "react-dom/no-render-return-value": "error", "react-dom/no-script-url": "warn", "react-dom/no-unsafe-iframe-sandbox": "warn", "react-dom/no-unsafe-target-blank": "warn", "react-dom/no-use-form-state": "error", "react-dom/no-void-elements-with-children": "error", "react-hooks/exhaustive-deps": "warn", "react-hooks/rules-of-hooks": "error", "react-hooks/react-compiler": "warn", "react-hooks-extra/no-direct-set-state-in-use-effect": "warn", "react-hooks-extra/no-unnecessary-use-prefix": "warn", "react-web-api/no-leaked-event-listener": "warn", "react-web-api/no-leaked-interval": "warn", "react-web-api/no-leaked-resize-observer": "warn", "react-web-api/no-leaked-timeout": "warn", "react-refresh/only-export-components": ["warn", { allowConstantExport: isAllowConstantExport, allowExportNames: [...isUsingNext ? [ "dynamic", "dynamicParams", "revalidate", "fetchCache", "runtime", "preferredRegion", "maxDuration", "config", "generateStaticParams", "metadata", "generateMetadata", "viewport", "generateViewport" ] : [], ...isUsingRemix || isUsingReactRouter ? [ "meta", "links", "headers", "loader", "action" ] : []] }], ...overrides } }, ...isTypeAware ? [{ files: filesTypeAware, ignores: ignoresTypeAware, name: "antfu/react/type-aware-rules", rules: { ...typeAwareRules } }] : [] ]; } //#endregion //#region src/configs/regexp.ts function regexp(options = {}) { const config = configs["flat/recommended"]; const rules = { ...config.rules }; if (options.level === "warn") Object.keys(rules).forEach((key) => { if (rules[key] === "error") rules[key] = "warn"; }); return [{ ...config, name: "coderwyd/regexp/rules", rules: { ...rules, ...options.overrides } }]; } //#endregion //#region src/configs/sort.ts /** * Sort package.json * * Requires `jsonc` config */ function sortPackageJson() { return [{ files: ["**/package.json"], name: "coderwyd/sort/package-json", rules: { "jsonc/sort-array-values": ["error", { order: { type: "asc" }, pathPattern: "^files$" }], "jsonc/sort-keys": [ "error", { order: [ "publisher", "name", "displayName", "type", "version", "private", "packageManager", "description", "author", "license", "funding", "homepage", "repository", "bugs", "keywords", "categories", "sideEffects", "imports", "exports", "main", "module", "unpkg", "jsdelivr", "types", "typesVersions", "bin", "icon", "files", "engines", "activationEvents", "contributes", "scripts", "peerDependencies", "peerDependenciesMeta", "dependencies", "optionalDependencies", "devDependencies", "pnpm", "overrides", "resolutions", "husky", "simple-git-hooks", "lint-staged", "nano-staged", "eslintConfig" ], pathPattern: "^$" }, { order: { type: "asc" }, pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies(Meta)?$" }, { order: { type: "asc" }, pathPattern: "^(?:resolutions|overrides|pnpm.overrides)$" }, { order: [ "types", "import", "require", "default" ], pathPattern: "^exports.*$" }, { order: [ "pre-commit", "prepare-commit-msg", "commit-msg", "post-commit", "pre-rebase", "post-rewrite", "post-checkout", "post-merge", "pre-push", "pre-auto-gc" ], pathPattern: "^(?:gitHooks|husky|simple-git-hooks)$" } ] } }]; } /** * Sort tsconfig.json * * Requires `jsonc` config */ function sortTsconfig() { return [{ files: ["**/tsconfig.json", "**/tsconfig.*.json"], name: "coderwyd/sort/tsconfig-json", rules: { "jsonc/sort-keys": [ "error", { order: [ "extends", "compilerOptions", "references", "files", "include", "exclude" ], pathPattern: "^$" }, { order: [ "incremental", "composite", "tsBuildInfoFile", "disableSourceOfProjectReferenceRedirect", "disableSolutionSearching", "disableReferencedProjectLoad", "target", "jsx", "jsxFactory", "jsxFragmentFactory", "jsxImportSource", "lib", "moduleDetection", "noLib", "reactNamespace", "useDefineForClassFields", "emitDecoratorMetadata", "experimentalDecorators", "libReplacement", "baseUrl", "rootDir", "rootDirs", "customConditions", "module", "moduleResolution", "moduleSuffixes", "noResolve", "paths", "resolveJsonModule", "resolvePackageJsonExports", "resolvePackageJsonImports", "typeRoots", "types", "allowArbitraryExtensions", "allowImportingTsExtensions", "allowUmdGlobalAccess", "allowJs", "checkJs", "maxNodeModuleJsDepth", "strict", "strictBindCallApply", "strictFunctionTypes", "strictNullChecks", "strictPropertyInitialization", "allowUnreachableCode", "allowUnusedLabels", "alwaysStrict", "exactOptionalPropertyTypes", "noFallthroughCasesInSwitch", "noImplicitAny", "noImplicitOverride", "noImplicitReturns", "noImplicitThis", "noPropertyAccessFromIndexSignature", "noUncheckedIndexedAccess", "noUnusedLocals", "noUnusedParameters", "useUnknownInCatchVariables", "declaration", "declarationDir", "declarationMap", "downlevelIteration", "emitBOM", "emitDeclarationOnly", "importHelpers", "importsNotUsedAsValues", "inlineSourceMap", "inlineSources", "mapRoot", "newLine", "noEmit", "noEmitHelpers", "noEmitOnError", "outDir", "outFile", "preserveConstEnums", "preserveValueImports", "removeComments", "sourceMap", "sourceRoot", "stripInternal", "allowSyntheticDefaultImports", "esModuleInterop", "forceConsistentCasingInFileNames", "isolatedDeclarations", "isolatedModules", "preserveSymlinks", "verbatimModuleSyntax", "erasableSyntaxOnly", "skipDefaultLibCheck", "skipLibCheck" ], pathPattern: "^compilerOptions$" } ] } }]; } //#endregion //#region src/configs/specials.ts function specials() { return [ { files: [`**/scripts/${GLOB_SRC}`], name: "coderwyd/specials/scripts", rules: { "antfu/no-top-level-await": "off", "no-console": "off", "ts/explicit-function-return-type": "off" } }, { files: [`**/cli/${GLOB_SRC}`, `**/cli.${GLOB_SRC_EXT}`], name: "coderwyd/specials/cli", rules: { "antfu/no-top-level-await": "off", "no-console": "off" } }, { files: ["**/bin/**/*", `**/bin.${GLOB_SRC_EXT}`], name: "coderwyd/specials/bin", rules: { "antfu/no-import-dist": "off", "antfu/no-import-node-modules-by-path": "off" } }, { files: [GLOB_DTS], name: "coderwyd/specials/dts", rules: { "eslint-comments/no-unlimited-disable": "off", "import/no-duplicates": "off", "no-restricted-syntax": "off", "unused-imports/no-unused-vars": "off" } }, { files: ["**/*.{test,spec}.([tj])s?(x)"], name: "coderwyd/specials/test", rules: { "antfu/no-top-level-await": "off", "no-unused-expressions": "off" } }, { files: ["**/*.js", "**/*.cjs"], name: "coderwyd/specials/cjs", rules: { "ts/no-require-imports": "off" } } ]; } //#endregion //#region src/configs/svelte.ts async function svelte(options = {}) { const { files = [GLOB_SVELTE], overrides = {} } = options; await ensurePackages(["eslint-plugin-svelte"]); const [pluginSvelte, parserSvelte] = await Promise.all([interopDefault(import("eslint-plugin-svelte")), interopDefault(import("svelte-eslint-parser"))]); return [{ name: "coderwyd/svelte/setup", plugins: { svelte: pluginSvelte } }, { files, languageOptions: { parser: parserSvelte, parserOptions: { extraFileExtensions: [".svelte"], parser: options.typescript ? await interopDefault(import("@typescript-eslint/parser")) : null } }, name: "coderwyd/svelte/rules", processor: pluginSvelte.processors[".svelte"], rules: { "import/no-mutable-exports": "off", "no-undef": "off", "no-unused-vars": ["error", { args: "none", caughtErrors: "none", ignoreRestSiblings: true, vars: "all", varsIgnorePattern: "^(\\$\\$Props$|\\$\\$Events$|\\$\\$Slots$)" }], "svelte/comment-directive": "error", "svelte/no-at-debug-tags": "warn", "svelte/no-at-html-tags": "error", "svelte/no-dupe-else-if-blocks": "error", "svelte/no-dupe-style-properties": "error", "svelte/no-dupe-use-directives": "error", "svelte/no-dynamic-slot-name": "error", "svelte/no-export-load-in-svelte-module-in-kit-pages": "error", "svelte/no-inner-declarations": "error", "svelte/no-not-function-handler": "error", "svelte/no-object-in-text-mustaches": "error", "svelte/no-reactive-functions": "error", "svelte/no-reactive-literals": "error", "svelte/no-shorthand-style-property-overrides": "error", "svelte/no-unknown-style-directive-property": "error", "svelte/no-unused-svelte-ignore": "error", "svelte/no-useless-mustaches": "error", "svelte/require-store-callbacks-use-set-param": "error", "svelte/system": "error", "svelte/valid-each-key": "error", "unused-imports/no-unused-vars": ["error", { args: "after-used", argsIgnorePattern: "^_", vars: "all", varsIgnorePattern: "^(_|\\$\\$Props$|\\$\\$Events$|\\$\\$Slots$)" }], ...overrides } }]; } //#endregion //#region src/configs/tailwindcss.ts async function tailwindcss(options = {}) { const { overrides } = options; await ensurePackages(["eslint-plugin-tailwindcss"]); const pluginTailwindcss = await interopDefault(import("eslint-plugin-tailwindcss")); return [{ name: "coderwyd/tailwindcss/rules", plugins: { tailwindcss: pluginTailwindcss }, rules: { "tailwindcss/classnames-order": "warn", "tailwindcss/enforces-negative-arbitrary-values": "warn", "tailwindcss/enforces-shorthand": "warn", "tailwindcss/migration-from-tailwind-2": "warn", "tailwindcss/no-arbitrary-value": "off", "tailwindcss/no-contradicting-classname": "warn", "tailwindcss/no-custom-classname": "off", "tailwindcss/no-unnecessary-arbitrary-value": "warn", ...overrides } }]; } //#endregion //#region src/configs/test.ts let _pluginTest; async function test(options = {}) { const { files = GLOB_TESTS, isInEditor: isInEditor$1 = false, overrides = {} } = options; const [pluginVitest, pluginNoOnlyTests] = await Promise.all([interopDefault(import("@vitest/eslint-plugin")), interopDefault(import("eslint-plugin-no-only-tests"))]); _pluginTest = _pluginTest || { ...pluginVitest, rules: { ...pluginVitest.rules, ...pluginNoOnlyTests.rules } }; return [{ name: "coderwyd/test/setup", plugins: { test: _pluginTest } }, { files, name: "coderwyd/test/rules", rules: { "node/prefer-global/process": "off", "test/consistent-test-it": ["error", { fn: "it", withinDescribe: "it" }], "test/no-identical-title": "error", "test/no-import-node-test": "error", "test/no-only-tests": isInEditor$1 ? "off" : "error", "test/prefer-hooks-in-order": "error", "test/prefer-lowercase-title": "error", "ts/explicit-function-return-type": "off", ...overrides } }]; } //#endregion //#region src/configs/typescript.ts async function typescript(options = {}) { const { componentExts = [], overrides = {}, overridesTypeAware = {}, parserOptions = {} } = options; const files = options.files ?? [ GLOB_TS, GLOB_TSX, ...componentExts.map((ext) => `**/*.${ext}`) ]; const filesTypeAware = options.filesTypeAware ?? [GLOB_TS, GLOB_TSX]; const ignoresTypeAware = options.ignoresTypeAware ?? [`${GLOB_MARKDOWN}/**`, GLOB_ASTRO_TS]; const tsconfigPath = options.tsconfigPath; const isTypeAware = !!tsconfigPath; const typeAwareRules = { "dot-notation": "off", "no-implied-eval": "off", "ts/await-thenable": "error", "ts/dot-notation": ["error", { allowKeywords: true }], "ts/no-floating-promises": "error", "ts/no-for-in-array": "error", "ts/no-implied-eval": "error", "ts/no-misused-promises": "error", "ts/no-unnecessary-type-assertion": "error", "ts/no-unsafe-argument": "error", "ts/no-unsafe-assignment": "error", "ts/no-unsafe-call": "error", "ts/no-unsafe-member-access": "error", "ts/no-unsafe-return": "error", "ts/promise-function-async": "error", "ts/restrict-plus-operands": "error", "ts/restrict-template-expressions": "error", "ts/return-await": "error", "ts/strict-boolean-expressions": "error", "ts/switch-exhaustiveness-check": "error", "ts/unbound-method": "error" }; const [pluginTs, parserTs] = await Promise.all([interopDefault(import("@typescript-eslint/eslint-plugin")), interopDefault(import("@typescript-eslint/parser"))]); function makeParser(typeAware, files$1, ignores$1) { return { files: files$1, ...ignores$1 ? { ignores: ignores$1 } : {}, languageOptions: { parser: parserTs, parserOptions: { extraFileExtensions: componentExts.map((ext) => `.${ext}`), sourceType: "module", ...typeAware ? { projectService: { allowDefaultProject: ["./*.js"], defaultProject: tsconfigPath }, tsconfigRootDir: process.cwd() } : {}, ...parserOptions } }, name: `coderwyd/typescript/${typeAware ? "type-aware-parser" : "parser"}` }; } return [ { name: "coderwyd/typescript/setup", plugins: { antfu: pluginAntfu, ts: pluginTs } }, ...isTypeAware ? [makeParser(false, files), makeParser(true, filesTypeAware, ignoresTypeAware)] : [makeParser(false, files)], { files, name: "coderwyd/typescript/rules", rules: { ...renameRules(pluginTs.configs["eslint-recommended"].overrides[0].rules, { "@typescript-eslint": "ts" }), ...renameRules(pluginTs.configs.strict.rules, { "@typescript-eslint": "ts" }), "no-dupe-class-members": "off", "no-redeclare": "off", "no-use-before-define": "off", "no-useless-constructor": "off", "ts/ban-ts-comment": ["error", { "ts-expect-error": "allow-with-description" }], "ts/consistent-type-definitions": ["error", "interface"], "ts/consistent-type-imports": ["error", { disallowTypeAnnotations: false, fixStyle: "separate-type-imports", prefer: "type-imports" }], "ts/method-signature-style": ["error", "property"], "ts/no-dupe-class-members": "error", "ts/no-dynamic-delete": "off", "ts/no-empty-object-type": ["error", { allowInterfaces: "with-single-extends", allowObjectTypes: "never", allowWithName: "Props$" }], "ts/no-explicit-any": "off", "ts/no-extraneous-class": "off", "ts/no-import-type-side-effects": "error", "ts/no-invalid-void-type": "off", "ts/no-non-null-assertion": "off", "ts/no-redeclare": ["error", { builtinGlobals: false }], "ts/no-require-imports": "error", "ts/no-unused-expressions": ["error", { allowShortCircuit: true, allowTaggedTemplates: true, allowTernary: true }], "ts/no-unused-vars": "off", "ts/no-use-before-define": ["error", { classes: false, functions: false, variables: true }], "ts/no-useless-constructor": "off", "ts/no-wrapper-object-types": "error", "ts/triple-slash-reference": "off", "ts/unified-signatures": "off", ...overrides } }, ...isTypeAware ? [{ files: filesTypeAware, ignores: ignoresTypeAware, name: "coderwyd/typescript/rules-type-aware", rules: { ...typeAwareRules, ...overridesTypeAware } }] : [] ]; } //#endregion //#region src/configs/unicorn.ts function unicorn(options = {}) { const { overrides = {} } = options; return [{ name: "coderwyd/unicorn/rules", plugins: { unicorn: pluginUnicorn }, rules: { "unicorn/catch-error-name": "error", "unicorn/consistent-empty-array-spread": "error", "unicorn/consistent-existence-index-check": "error", "unicorn/custom-error-definition": "error", "unicorn/error-message": "error", "unicorn/escape-case": "error", "unicorn/new-for-builtins": "error", "unicorn/no-array-method-this-argument": "error", "unicorn/no-await-in-promise-methods": "error", "unicorn/no-console-spaces": "error", "unicorn/no-for-loop": "error", "unicorn/no-hex-escape": "error", "unicorn/no-instanceof-builtins": "error", "unicorn/no-invalid-remove-event-listener": "error", "unicorn/no-lonely-if": "error", "unicorn/no-negation-in-equality-check": "error", "unicorn/no-new-array": "error", "unicorn/no-new-buffer": "error", "unicorn/no-single-promise-in-promise-methods": "error", "unicorn/no-static-only-class": "error", "unicorn/no-unnecessary-array-flat-depth": "error", "unicorn/no-unnecessary-array-splice-count": "error", "unicorn/no-unnecessary-await": "error", "unicorn/no-unnecessary-slice-end": "error", "unicorn/no-zero-fractions": "error", "unicorn/prefer-add-event-listener": "error", "unicorn/prefer-array-find": "error", "unicorn/prefer-array-flat-map": "error", "unicorn/prefer-array-index-of": "error", "unicorn/prefer-array-some": "error", "unicorn/prefer-at": "error", "unicorn/prefer-blob-reading-methods": "error", "unicorn/prefer-date-now": "error", "unicorn/prefer-dom-node-append": "error", "unicorn/prefer-dom-node-dataset": "error", "unicorn/prefer-dom-node-remove": "error", "unicorn/prefer-dom-node-text-content": "error", "unicorn/prefer-includes": "error", "unicorn/prefer-keyboard-event-key": "error", "unicorn/prefer-math-min-max": "error", "unicorn/prefer-math-trunc": "error", "unicorn/prefer-modern-dom-apis": "error", "unicorn/prefer-modern-math-apis": "error", "unicorn/prefer-negative-index": "error", "unicorn/prefer-node-protocol": "error", "unicorn/prefer-number-properties": "error", "unicorn/prefer-optional-catch-binding": "error", "unicorn/prefer-prototype-methods": "error", "unicorn/prefer-query-selector": "error", "unicorn/prefer-reflect-apply": "error", "unicorn/prefer-single-call": "error", "unicorn/prefer-string-replace-all": "error", "unicorn/prefer-string-slice": "error", "unicorn/prefer-string-starts-ends-with": "error", "unicorn/prefer-string-trim-start-end": "error", "unicorn/prefer-type-error": "error", "unicorn/throw-new-error": "error", ...overrides } }]; } //#endregion //#region src/configs/unocss.ts async function unocss(options = {}) { const { attributify = true, strict = false } = options; await ensurePackages(["@unocss/eslint-plugin"]); const [pluginUnoCSS] = await Promise.all([interopDefault(import("@unocss/eslint-plugin"))]); return [{ name: "coderwyd/unocss/rules", plugins: { unocss: pluginUnoCSS }, rules: { "unocss/order": "warn", ...attributify ? { "unocss/order-attributify": "warn" } : {}, ...strict ? { "unocss/blocklist": "error" } : {} } }]; } //#endregion //#region src/configs/vue.ts async function vue(options = {}) { const { files = [GLOB_VUE], overrides = {} } = options; const [pluginVue, parserVue] = await Promise.all([interopDefault(import("eslint-plugin-vue")), interopDefault(import("vue-eslint-parser"))]); const isVue3 = getVueVersion() === 3; const configKeys = isVue3 ? [ "essential", "strongly-recommended", "recommended" ] : [ "vue2-essential", "vue2-strongly-recommended", "vue2-recommended" ]; const vueRules = configKeys.reduce((preRules, key) => { const config = pluginVue.configs[key]; return { ...preRules, ...config.rules }; }, {}); return [{ files, languageOptions: { parser: parserVue, parserOptions: { ecmaFeatures: { jsx: true }, extraFileExtensions: [".vue"], parser: options.typescript ? await interopDefault(import("@typescript-eslint/parser")) : null, sourceType: "module" } }, name: "coderwyd/vue/rules", plugins: { vue: pluginVue }, processor: pluginVue.processors[".vue"], rules: { ...pluginVue.configs.base.rules, ...vueRules, "antfu/no-top-level-await": "off", "node/prefer-global/process": "off", "ts/explicit-function-return-type": "off", "vue/block-order": ["error", { order: isVue3 ? [ "script", "template", "style" ] : [ "template", "script", "style" ] }], "vue/component-name-in-template-casing": ["error", "PascalCase"], "vue/component-options-name-casing": ["error", "PascalCase"], "vue/component-tags-order": "off", "vue/custom-event-name-casing": ["error", "camelCase"], "vue/define-macros-order": ["error", { order: [ "defineOptions", "defineProps", "defineEmits", "defineSlots" ] }], "vue/eqeqeq": ["error", "smart"], "vue/html-self-closing": ["error", { html: { component: "always", normal: "always", void: "any" }, math: "always", svg: "always" }], "vue/max-attributes-per-line": "off", "vue/multi-word-component-names": "off", "vue/no-constant-condition": "warn", "vue/no-duplicate-attr-inheritance": "warn", "vue/no-empty-pattern": "error", "vue/no-extra-parens": ["error", "functions"], "vue/no-irregular-whitespace": "error", "vue/no-loss-of-precision": "error", "vue/no-required-prop-with-default": "warn", "vue/no-restricted-syntax": [ "error", "DebuggerStatement", "LabeledStatement", "WithStatement" ], "vue/no-restricted-v-bind": ["error", "/^v-/"], "vue/no-setup-props-reactivity-loss": "off", "vue/no-sparse-arrays": "error", "vue/no-unsupported-features": "warn", "vue/no-unused-emit-declarations": "warn", "vue/no-unused-refs": "error", "vue/no-use-v-else-with-v-for": "error", "vue/no-useless-mustaches": "warn", "vue/no-useless-v-bind": "error", "vue/no-v-html": "off", "vue/no-v-text": "warn", "vue/object-shorthand": [ "error", "always", { avoidQuotes: true, ignoreConstructors: false } ], "vue/padding-line-between-blocks": ["error", "always"], "vue/prefer-define-options": "warn", "vue/prefer-separate-static-class": "error", "vue/prefer-template": "error", "vue/prop-name-casing": ["error", "camelCase"], "vue/require-default-prop": "off", "vue/require-macro-variable-name": ["warn", { defineEmits: "emit", defineProps: "props", defineSlots: "slots", useAttrs: "attrs", useSlots: "slots" }], "vue/require-prop-types": "off", "vue/space-infix-ops": "error", "vue/space-unary-ops": ["error", { nonwords: false, words: true }], "vue/valid-define-options": "warn", ...overrides } }]; } //#endregion //#region src/configs/yaml.ts async function yaml(options = {}) { const { files = [GLOB_YAML], overrides = {} } = options; const [pluginYaml, parserYaml] = await Promise.all([interopDefault(import("eslint-plugin-yml")), interopDefault(import("yaml-eslint-parser"))]); return [ { name: "coderwyd/yaml/setup", plugins: { yaml: pluginYaml } }, { files, languageOptions: { parser: parserYaml }, name: "coderwyd/yaml/rules", rules: { "yaml/block-mapping": "error", "yaml/block-sequence": "error", "yaml/no-empty-key": "error", "yaml/no-empty-sequence-entry": "error", "yaml/no-irregular-whitespace": "error", "yaml/plain-scalar": "error", "yaml/vue-custom-block/no-parsing-error": "error", ...overrides } }, { files: ["pnpm-workspace.yaml"], name: "coderwyd/yaml/pnpm-workspace", rules: { "yaml/sort-keys": [ "error", { order: [ "packages", "overrides", "patchedDependencies", "hoistPattern", "catalog", "catalogs", "allowedDeprecatedVersions", "allowNonAppliedPatches", "configDependencies", "ignoredBuiltDependencies", "ignoredOptionalDependencies", "neverBuiltDependencies", "onlyBuiltDependencies", "onlyBuiltDependenciesFile", "packageExtensions", "peerDependencyRules", "supportedArchitectures" ], pathPattern: "^$" }, { order: { type: "asc" }, pathPattern: ".*" } ] } } ]; } //#endregion //#region src/index.ts const flatConfigProps = [ "name", "languageOptions", "linterOptions", "processor", "plugins", "rules", "settings" ]; const defaultPluginRenaming = { "@eslint-react": "react", "@eslint-react/dom": "react-dom", "@eslint-react/hooks-extra": "react-hooks-extra", "@eslint-react/naming-convention": "react-naming-convention", "@typescript-eslint": "ts", "import-lite": "import", n: "node", vitest: "test", yml: "yaml" }; /** * Construct an array of ESLint flat config items. * * @param {OptionsConfig & TypedFlatConfigItem} options * The options for generating the ESLint configurations. * @param {Awaitable<TypedFlatConfigItem | TypedFlatConfigItem[] | Linter.Config[]>[]} userConfigs * The user configurations to be merged with the generated configurations. * @returns {Promise<TypedFlatConfigItem[]>} * The merged ESLint configurations. */ async function defineConfig(options = {}, ...userConfigs) { const { autoRenamePlugins = true, componentExts = [], gitignore: enableGitignore = true, react: enableReact = false, regexp: enableRegexp = true, svelte: enableSvelte = false, tailwindcss: enableTailwindCSS = false, typescript: enableTypeScript = isUsingTypeScript, unocss: enableUnoCSS = false, vue: enableVue = isUsingVue } = options; let isInEditor$1 = options.isInEditor; if (isInEditor$1 == null) { isInEditor$1 = isInEditorEnv(); if (isInEditor$1) console.log("[@coderwyd/eslint-config] Detected running in editor, some rules are disabled."); } const configs$1 = []; if (enableGitignore) if (typeof enableGitignore !== "boolean") configs$1.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({ ...enableGitignore, name: "coderwyd/gitignore" })])); else configs$1.push(interopDefault(import("eslint-config-flat-gitignore")).then((r) => [r({ name: "coderwyd/gitignore", strict: false })])); const typescriptOptions = resolveSubOptions(options, "typescript"); const tsconfigPath = "tsconfigPath" in typescriptOptions ? typescriptOptions.tsconfigPath : void 0; configs$1.push(ignores(options.ignores), javascript({ isInEditor: isInEditor$1, overrides: getOverrides(options, "javascript") }), comments(), node(), jsdoc(), imports(), unicorn(), command(), deMorgan(), perfectionist()); if (enableVue) componentExts.push("vue"); if (enableTypeScript) configs$1.push(typescript({ ...typescriptOptions, componentExts, overrides: getOverrides(options, "typescript") })); if (enableRegexp) configs$1.push(regexp(typeof enableRegexp === "boolean" ? {} : enableRegexp)); if (options.test ?? true) configs$1.push(test({ isInEditor: isInEditor$1, overrides: