UNPKG

@sankeyangshu/eslint-config

Version:
1,552 lines (1,528 loc) 45.8 kB
// src/index.ts import { FlatConfigComposer } from "eslint-flat-config-utils"; // src/constants/index.ts var GLOB_SRC_EXT = "?([cm])[jt]s?(x)"; var GLOB_SRC = "**/*.?([cm])[jt]s?(x)"; var GLOB_JSX = "**/*.?([cm])jsx"; var GLOB_TSX = "**/*.?([cm])tsx"; var GLOB_VUE = "**/*.vue"; var GLOB_ASTRO = "**/*.astro"; var GLOB_SVELTE = "**/*.svelte"; var GLOB_HTML = "**/*.htm?(l)"; var GLOB_CSS = "**/*.css"; var GLOB_POSTCSS = "**/*.{p,post}css"; var GLOB_LESS = "**/*.less"; var GLOB_SCSS = "**/*.scss"; var GLOB_JSON = "**/*.json"; var GLOB_JSON5 = "**/*.json5"; var GLOB_JSONC = "**/*.jsonc"; var GLOB_MARKDOWN = "**/*.md"; var GLOB_YAML = "**/*.y?(a)ml"; var GLOB_TOML = "**/*.toml"; var GLOB_PRETTIER_LINT = [GLOB_SRC, GLOB_VUE]; var GLOB_TESTS = [ `**/__tests__/**/*.${GLOB_SRC_EXT}`, `**/*.spec.${GLOB_SRC_EXT}`, `**/*.test.${GLOB_SRC_EXT}`, `**/*.bench.${GLOB_SRC_EXT}`, `**/*.benchmark.${GLOB_SRC_EXT}` ]; var 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", "**/.vercel", "**/.changeset", "**/.idea", "**/.cache", "**/.output", "**/.vite-inspect", "**/CHANGELOG*.md", "**/*.min.*", "**/LICENSE*", "**/__snapshots__", "**/auto-import?(s).d.ts", "**/components.d.ts", "**/.github/workflows/*.yml" ]; var DEFAULT_PRETTIER_RULES = { useTabs: false, tabWidth: 2, printWidth: 100, singleQuote: true, trailingComma: "es5", bracketSpacing: true, semi: true, jsdocCapitalizeDescription: false }; // src/utils/index.ts import { readFile } from "node:fs/promises"; import path from "node:path"; import process from "node:process"; import { isPackageExists } from "local-pkg"; async function interopDefault(m) { const resolved = await m; return resolved.default || resolved; } async function ensurePackages(packages) { if (process.env.CI || process.stdout.isTTY === false) return; const nonExistingPackages = packages.filter((i) => !isPackageExists(i)); if (nonExistingPackages.length === 0) return; const { default: prompts } = await import("prompts"); const message = `${nonExistingPackages.length === 1 ? "Package is" : "Packages are"} required for this config: ${nonExistingPackages.join(", ")}. Do you want to install them?`; const { result } = await prompts([ { message, name: "result", type: "confirm" } ]); if (result) { const { installPackage } = await import("@antfu/install-pkg"); await installPackage(nonExistingPackages, { dev: true }); } } async function loadPrettierConfig(cwd) { let prettierConfig = {}; try { const prettierrc = await readFile(path.join(cwd, ".prettierrc"), "utf-8"); prettierConfig = JSON.parse(prettierrc); } catch { } return prettierConfig; } function getOverridesRules(overrides = {}) { const overrideRecord = { js: {} }; const rulePrefixes = { ts: "@typescript-eslint/", import: "import/", perfectionist: "perfectionist/", jsdoc: "jsdoc/", jsonc: "jsonc/", n: "n/", unicorn: "unicorn/", vue: "vue/", "react-native": "react-native/", react: "react/", astro: "astro/", svelte: "svelte/", solid: "solid/", unocss: "unocss/" }; const overrideRuleKeys = Object.keys(rulePrefixes); overrideRuleKeys.forEach((key) => { overrideRecord[key] = {}; }); const ruleKeys = Object.keys(overrides); ruleKeys.forEach((key) => { const hasMatch = overrideRuleKeys.some((overrideKey) => { const prefix = rulePrefixes[overrideKey]; const matched = key.startsWith(prefix); if (matched) { overrideRecord[overrideKey][key] = overrides[key]; } return matched; }); if (!hasMatch) { overrideRecord.js[key] = overrides[key]; } }); return overrideRecord; } // src/configs/typescript.ts async function createTypescriptRules() { const pluginTs = await interopDefault(import("@typescript-eslint/eslint-plugin")); const { rules: recommendedRules } = pluginTs.configs["eslint-recommended"].overrides[0]; const tsRules = { ...pluginTs.configs.base.rules, ...recommendedRules, ...pluginTs.configs.strict.rules, "@typescript-eslint/consistent-type-imports": [ "error", { prefer: "type-imports", disallowTypeAnnotations: false } ], "@typescript-eslint/no-empty-interface": [ "error", { allowSingleExtends: true } ], // Override JS "no-redeclare": "off", "@typescript-eslint/no-redeclare": "error", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": [ "error", { vars: "all", args: "all", ignoreRestSiblings: false, varsIgnorePattern: "^_", argsIgnorePattern: "^_" } ], "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": [ "error", { functions: false, classes: false, variables: true } ], "no-shadow": "off", "@typescript-eslint/no-shadow": "error", // off "@typescript-eslint/ban-types": "off", "@typescript-eslint/consistent-type-definitions": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/unified-signatures": "off" }; return tsRules; } async function createTypescriptConfig(overrides = {}) { const [pluginTs, parserTs] = await Promise.all([ interopDefault(import("@typescript-eslint/eslint-plugin")), interopDefault(import("@typescript-eslint/parser")) ]); const tsRules = await createTypescriptRules(); return [ { files: [GLOB_SRC], languageOptions: { parser: parserTs, parserOptions: { sourceType: "module" } }, plugins: { "@typescript-eslint": pluginTs }, rules: { ...tsRules, ...overrides } }, { files: ["**/*.js", "**/*.cjs"], rules: { "@typescript-eslint/no-require-imports": "off", "@typescript-eslint/no-var-requires": "off" } } ]; } // src/configs/astro.ts async function createAstroConfig(options, prettierRules2 = {}, overrides = {}) { if (!options) return []; const { files = [GLOB_ASTRO] } = options; await ensurePackages(["eslint-plugin-astro", "astro-eslint-parser", "prettier-plugin-astro"]); const [pluginAstro, pluginTs, pluginPrettier] = await Promise.all([ interopDefault(import("eslint-plugin-astro")), interopDefault(import("@typescript-eslint/eslint-plugin")), interopDefault(import("eslint-plugin-prettier")) ]); const tsRules = await createTypescriptRules(); const { plugins = [] } = prettierRules2; const pRules = { ...prettierRules2, plugins: plugins.concat("prettier-plugin-astro") }; return [ ...pluginAstro.configs.recommended, { files, plugins: { "@typescript-eslint": pluginTs, prettier: pluginPrettier }, rules: { ...tsRules, ...overrides, "prettier/prettier": [ "warn", { ...pRules, parser: "astro" } ] } } ]; } // src/configs/disables.ts async function createDisablesConfig() { return [ { files: [`**/scripts/${GLOB_SRC}`], rules: { "no-console": "off", "ts/explicit-function-return-type": "off" } }, { files: [`**/cli/${GLOB_SRC}`, `**/cli.${GLOB_SRC_EXT}`], rules: { "no-console": "off" } }, { files: ["**/*.d.?([cm])ts"], rules: { "eslint-comments/no-unlimited-disable": "off", "import/no-duplicates": "off", "no-restricted-syntax": "off", "unused-imports/no-unused-vars": "off" } }, { files: ["**/*.js", "**/*.cjs"], rules: { "ts/no-require-imports": "off" } }, { files: [`**/*.config.${GLOB_SRC_EXT}`, `**/*.config.*.${GLOB_SRC_EXT}`], rules: { "no-console": "off", "ts/explicit-function-return-type": "off" } } ]; } // src/configs/formatter.ts async function createFormatterConfig(options, prettierRules2 = {}) { const { html = true, css = true, markdown, yaml, toml } = options || {}; const [pluginPrettier, parserPlain] = await Promise.all([ interopDefault(import("eslint-plugin-prettier")), interopDefault(import("eslint-parser-plain")) ]); function createPrettierFormatter(files, parser, plugins) { const rules = { ...prettierRules2, parser }; if (plugins?.length) { rules.plugins = [...rules.plugins || [], ...plugins]; } const config = { files, languageOptions: { parser: parserPlain }, plugins: { prettier: pluginPrettier }, rules: { "prettier/prettier": ["warn", rules] } }; return config; } const configs = []; if (css) { const cssConfig = createPrettierFormatter([GLOB_CSS, GLOB_POSTCSS], "css"); const scssConfig = createPrettierFormatter([GLOB_SCSS], "scss"); const lessConfig = createPrettierFormatter([GLOB_LESS], "less"); configs.push(cssConfig, scssConfig, lessConfig); } if (html) { const htmlConfig = createPrettierFormatter([GLOB_HTML], "html"); configs.push(htmlConfig); } if (markdown) { const markdownConfig = createPrettierFormatter([GLOB_MARKDOWN], "markdown"); configs.push(markdownConfig); } if (yaml) { const yamlConfig = createPrettierFormatter([GLOB_YAML], "yaml"); configs.push(yamlConfig); } if (toml) { await ensurePackages(["@toml-tools/parser", "prettier-plugin-toml"]); const tomlConfig = createPrettierFormatter([GLOB_TOML], "toml", ["prettier-plugin-toml"]); configs.push(tomlConfig); } return configs; } // src/configs/gitignore.ts async function createGitIgnoresConfig(options) { if (!options) return []; const configs = []; const configItem = await interopDefault(import("eslint-config-flat-gitignore")).then((r) => [ r(typeof options !== "boolean" ? options : { strict: false }) ]); configs.push(...configItem); return configs; } async function createIgnoresConfig(userIgnores = []) { return [ { ignores: [...userIgnores] } ]; } // src/configs/imports.ts async function createImportsConfig(overrides = {}) { const pluginImport = await interopDefault(import("eslint-plugin-import-x")); return [ { plugins: { import: pluginImport }, rules: { "import/first": "error", "import/newline-after-import": "error", "import/no-duplicates": "error", "import/no-mutable-exports": "error", "import/no-named-default": "error", "import/no-self-import": "error", "import/no-webpack-loader-syntax": "error", "import/order": "off", ...overrides } } ]; } // src/configs/javascript.ts import jsRules from "@eslint/js"; import pluginUnusedImports from "eslint-plugin-unused-imports"; import globals from "globals"; async function createJavascriptConfig(overrides = {}) { return [ { languageOptions: { ecmaVersion: "latest", globals: { ...globals.browser, ...globals.es2025, ...globals.node, document: "readonly", navigator: "readonly", window: "readonly" }, parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: "latest", sourceType: "module" }, sourceType: "module" }, linterOptions: { reportUnusedDisableDirectives: true }, plugins: { "unused-imports": pluginUnusedImports }, rules: { ...jsRules.configs.recommended.rules, "accessor-pairs": ["error", { enforceForClassMembers: true, setWithoutGet: true }], "array-callback-return": "error", "block-scoped-var": "error", "constructor-super": "error", "default-case-last": "error", "dot-notation": ["error", { allowKeywords: true }], eqeqeq: ["error", "always"], "keyword-spacing": "off", "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-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-function": "off", "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-object": "error", "no-new-symbol": "error", "no-new-wrappers": "error", "no-obj-calls": "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", "DebuggerStatement", "LabeledStatement", "WithStatement", "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": "off", "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": [ "error", { args: "none", caughtErrors: "none", ignoreRestSiblings: true, vars: "all" } ], "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-promise-reject-errors": "error", "prefer-regex-literals": ["error", { disallowRedundantWrapping: true }], "prefer-rest-params": "error", "prefer-spread": "error", "prefer-template": "error", "space-before-function-paren": "off", "spaced-comment": "error", "symbol-description": "error", "unicode-bom": ["error", "never"], "unused-imports/no-unused-imports": "error", "unused-imports/no-unused-vars": [ "error", { args: "after-used", argsIgnorePattern: "^_", vars: "all", varsIgnorePattern: "^_" } ], "use-isnan": ["error", { enforceForIndexOf: true, enforceForSwitchCase: true }], "valid-typeof": ["error", { requireStringLiterals: true }], "vars-on-top": "error", yoda: ["error", "never"], ...overrides } } ]; } // src/configs/jsdoc.ts async function createJsdocConfig(overrides = {}) { const pluginJsdoc = await interopDefault(import("eslint-plugin-jsdoc")); return [ { plugins: { jsdoc: pluginJsdoc }, 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", ...overrides } } ]; } // src/configs/jsonc.ts async function createJsoncConfig(overrides = {}) { const [pluginJsonc, parserJsonc] = await Promise.all([ interopDefault(import("eslint-plugin-jsonc")), interopDefault(import("jsonc-eslint-parser")) ]); return [ { plugins: { jsonc: pluginJsonc } }, { files: [GLOB_JSON, GLOB_JSON5, GLOB_JSONC], languageOptions: { parser: parserJsonc }, 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", "jsonc/array-bracket-spacing": ["error", "never"], "jsonc/comma-dangle": ["error", "never"], "jsonc/comma-style": ["error", "last"], "jsonc/indent": ["error", 2], "jsonc/key-spacing": ["error", { afterColon: true, beforeColon: false }], "jsonc/object-curly-newline": ["error", { consistent: true, multiline: true }], "jsonc/object-curly-spacing": ["error", "always"], "jsonc/object-property-newline": ["error", { allowMultiplePropertiesPerLine: true }], "jsonc/quote-props": "error", "jsonc/quotes": "error", ...overrides } } ]; } // src/configs/node.ts async function createNodeConfig(overrides = {}) { const pluginNode = await interopDefault(import("eslint-plugin-n")); return [ { plugins: { n: pluginNode }, rules: { "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/prefer-global/buffer": ["error", "never"], "n/prefer-global/process": ["error", "never"], "n/process-exit-as-throw": "error", ...overrides } } ]; } // src/configs/prettier.ts import prettierRules from "eslint-config-prettier"; var { rules: eslintRules } = prettierRules; async function createPrettierConfig(rules) { const pluginPrettier = await interopDefault(import("eslint-plugin-prettier")); const pRules = { ...rules // plugins: plugins.concat('prettier-plugin-jsdoc'), }; return [ { files: GLOB_PRETTIER_LINT, plugins: { prettier: pluginPrettier }, rules: { ...eslintRules, "prettier/prettier": ["warn", pRules], "arrow-body-style": "off", "prefer-arrow-callback": "off" } } ]; } // src/configs/react.ts import { isPackageExists as isPackageExists2 } from "local-pkg"; async function createReactConfig(options, overrides = {}) { if (!options) return []; const { files } = options; await ensurePackages([ "eslint-plugin-react", "eslint-plugin-react-hooks", "eslint-plugin-react-refresh" ]); const [pluginReact, pluginReactHooks, pluginReactRefresh] = await Promise.all([ interopDefault(import("eslint-plugin-react")), interopDefault(import("eslint-plugin-react-hooks")), interopDefault(import("eslint-plugin-react-refresh")) ]); const ReactRefreshAllowConstantExportPackages = ["vite"]; const isAllowConstantExport = ReactRefreshAllowConstantExportPackages.some( (i) => isPackageExists2(i) ); return [ { plugins: { react: pluginReact, "react-hooks": pluginReactHooks, "react-refresh": pluginReactRefresh }, settings: { react: { version: "detect" } } }, { files, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } } }, rules: { // recommended rules react-hooks "react-hooks/exhaustive-deps": "warn", "react-hooks/rules-of-hooks": "error", // react refresh "react-refresh/only-export-components": [ "warn", { allowConstantExport: isAllowConstantExport } ], ...pluginReact.configs.recommended.rules, // react runtime "react/react-in-jsx-scope": "off", "react/jsx-uses-react": "off", ...overrides } } ]; } // src/configs/reactNative.ts async function createReactNativeConfig(options, overrides = {}) { if (!options) return []; const { files } = options; await ensurePackages(["eslint-plugin-react-native"]); const pluginReactNative = await interopDefault(import("eslint-plugin-react-native")); return [ { plugins: { "react-native": pluginReactNative } }, { files, languageOptions: { parserOptions: { ecmaFeatures: { jsx: true } }, globals: { ...pluginReactNative.environments["react-native"].globals } }, rules: { ...pluginReactNative.configs.all.rules, ...overrides } } ]; } // src/configs/solid.ts async function createSolidConfig(options, overrides = {}) { if (!options) return []; const { files = [GLOB_JSX, GLOB_TSX], typescript = true } = options; await ensurePackages(["eslint-plugin-solid"]); const [pluginSolid, parserTs] = await Promise.all([ interopDefault(import("eslint-plugin-solid")), interopDefault(import("@typescript-eslint/parser")) ]); return [ { plugins: { solid: pluginSolid } }, { files, languageOptions: { parser: parserTs, parserOptions: { ecmaFeatures: { jsx: true } }, sourceType: "module" }, rules: { // reactivity "solid/components-return-once": "warn", "solid/event-handlers": [ "error", { // if true, don't warn on ambiguously named event handlers like `onclick` or `onchange` ignoreCase: false, // if true, warn when spreading event handlers onto JSX. Enable for Solid < v1.6. warnOnSpread: false } ], // these rules are mostly style suggestions "solid/imports": "error", // identifier usage is important "solid/jsx-no-duplicate-props": "error", "solid/jsx-no-script-url": "error", "solid/jsx-no-undef": "error", "solid/jsx-uses-vars": "error", "solid/no-destructure": "error", // security problems "solid/no-innerhtml": ["error", { allowStatic: true }], "solid/no-react-deps": "error", "solid/no-react-specific-props": "error", "solid/no-unknown-namespaces": "error", "solid/prefer-for": "error", "solid/reactivity": "warn", "solid/self-closing-comp": "error", "solid/style-prop": ["error", { styleProps: ["style", "css"] }], ...typescript ? { "solid/jsx-no-undef": ["error", { typescriptEnabled: true }], "solid/no-unknown-namespaces": "off" } : {}, // overrides ...overrides } } ]; } // src/configs/sort.ts async function createPerfectionistConfig(overrides = {}) { const pluginPerfectionist = await interopDefault(import("eslint-plugin-perfectionist")); return [ { plugins: { perfectionist: pluginPerfectionist }, rules: { "perfectionist/sort-imports": [ "warn", { groups: [ "builtin", "external", "internal", "internal-type", "parent", "parent-type", "sibling", "sibling-type", "index", "index-type", "object", "type", "side-effect", "side-effect-style" ], internalPattern: ["^[~@#]/.*"], newlinesBetween: "ignore" } ], "perfectionist/sort-named-exports": ["warn", { groupKind: "values-first" }], "perfectionist/sort-named-imports": ["warn", { groupKind: "values-first" }], ...overrides } } ]; } function createSortPackageJsonConfig() { return [ { files: ["**/package.json"], rules: { "jsonc/sort-array-values": [ "error", { order: { type: "asc" }, pathPattern: "^files$" } ], "jsonc/sort-keys": [ "error", { order: [ "name", "version", "type", "private", "packageManager", "description", "author", "license", "homepage", "repository", "bugs", "keywords", "contributors", "funding", "main", "module", "types", "files", "engines", "exports", "typesVersions", "sideEffects", "unpkg", "jsdelivr", "browser", "bin", "man", "directories", "publishConfig", "scripts", "peerDependencies", "peerDependenciesMeta", "optionalDependencies", "dependencies", "devDependencies", "pnpm", "config", "overrides", "husky", "simple-git-hooks", "lint-staged", "eslintConfig", "prettier" ], 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: [ // client hooks only "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)$" } ] } } ]; } function createSortTsConfig() { return [ { files: ["**/tsconfig.json", "**/tsconfig.*.json"], rules: { "jsonc/sort-keys": [ "error", { order: ["extends", "compilerOptions", "references", "files", "include", "exclude"], pathPattern: "^$" }, { order: [ /* Projects */ "incremental", "composite", "tsBuildInfoFile", "disableSourceOfProjectReferenceRedirect", "disableSolutionSearching", "disableReferencedProjectLoad", /* Language and Environment */ "target", "jsx", "jsxFactory", "jsxFragmentFactory", "jsxImportSource", "lib", "moduleDetection", "noLib", "reactNamespace", "useDefineForClassFields", "emitDecoratorMetadata", "experimentalDecorators", /* Modules */ "baseUrl", "rootDir", "rootDirs", "customConditions", "module", "moduleResolution", "moduleSuffixes", "noResolve", "paths", "resolveJsonModule", "resolvePackageJsonExports", "resolvePackageJsonImports", "typeRoots", "types", "allowArbitraryExtensions", "allowImportingTsExtensions", "allowUmdGlobalAccess", /* JavaScript Support */ "allowJs", "checkJs", "maxNodeModuleJsDepth", /* Type Checking */ "strict", "strictBindCallApply", "strictFunctionTypes", "strictNullChecks", "strictPropertyInitialization", "allowUnreachableCode", "allowUnusedLabels", "alwaysStrict", "exactOptionalPropertyTypes", "noFallthroughCasesInSwitch", "noImplicitAny", "noImplicitOverride", "noImplicitReturns", "noImplicitThis", "noPropertyAccessFromIndexSignature", "noUncheckedIndexedAccess", "noUnusedLocals", "noUnusedParameters", "useUnknownInCatchVariables", /* Emit */ "declaration", "declarationDir", "declarationMap", "downlevelIteration", "emitBOM", "emitDeclarationOnly", "importHelpers", "importsNotUsedAsValues", "inlineSourceMap", "inlineSources", "mapRoot", "newLine", "noEmit", "noEmitHelpers", "noEmitOnError", "outDir", "outFile", "preserveConstEnums", "preserveValueImports", "removeComments", "sourceMap", "sourceRoot", "stripInternal", /* Interop Constraints */ "allowSyntheticDefaultImports", "esModuleInterop", "forceConsistentCasingInFileNames", "isolatedDeclarations", "isolatedModules", "preserveSymlinks", "verbatimModuleSyntax", /* Completeness */ "skipDefaultLibCheck", "skipLibCheck" ], pathPattern: "^compilerOptions$" } ] } } ]; } // src/configs/svelte.ts async function createSvelteConfig(options, prettierRules2 = {}, overrides = {}) { if (!options) return []; const { files } = options; await ensurePackages(["eslint-plugin-svelte", "svelte-eslint-parser", "prettier-plugin-svelte"]); const [pluginSvelte, parserSvelte, pluginTs, pluginPrettier] = await Promise.all([ interopDefault(import("eslint-plugin-svelte")), interopDefault(import("svelte-eslint-parser")), interopDefault(import("@typescript-eslint/eslint-plugin")), interopDefault(import("eslint-plugin-prettier")) ]); const tsRules = await createTypescriptRules(); const { plugins = [] } = prettierRules2; const pRules = { ...prettierRules2, plugins: plugins.concat("prettier-plugin-svelte") }; return [ { files, languageOptions: { parser: parserSvelte, parserOptions: { extraFileExtensions: [".svelte"], parser: "@typescript-eslint/parser", sourceType: "module" } }, plugins: { "@typescript-eslint": pluginTs, svelte: pluginSvelte, prettier: pluginPrettier }, processor: pluginSvelte.processors.svelte, rules: { ...tsRules, ...pluginSvelte.configs["flat/recommended"].reduce( (acc, config) => ({ ...acc, ...config.rules }), {} ), ...overrides, "prettier/prettier": [ "warn", { ...pRules, parser: "svelte" } ] } } ]; } // src/configs/unicorn.ts async function createUnicornConfig(overrides = {}) { const pluginUnicorn = await interopDefault(import("eslint-plugin-unicorn")); return [ { plugins: { unicorn: pluginUnicorn }, rules: { "unicorn/error-message": "error", "unicorn/escape-case": "error", "unicorn/no-instanceof-array": "error", "unicorn/no-new-array": "error", "unicorn/no-new-buffer": "error", "unicorn/number-literal-case": "error", "unicorn/prefer-dom-node-text-content": "error", "unicorn/prefer-includes": "error", "unicorn/prefer-node-protocol": "error", "unicorn/prefer-number-properties": "error", "unicorn/prefer-string-starts-ends-with": "error", "unicorn/prefer-type-error": "error", "unicorn/throw-new-error": "error", ...overrides } } ]; } // src/configs/unocss.ts async function createUnoCssConfig(enable, overrides = {}) { if (!enable) return []; await ensurePackages(["@unocss/eslint-plugin"]); const pluginUnoCSS = await interopDefault(interopDefault(import("@unocss/eslint-plugin"))); return [ { plugins: { unocss: pluginUnoCSS }, rules: { "unocss/order": "warn", "unocss/order-attributify": "off", "unocss/blocklist": "off", ...overrides } } ]; } // src/configs/vue.ts async function createVueConfig(options, overrides = {}) { if (!options) return []; const { version = 3, files } = options; await ensurePackages(["eslint-plugin-vue", "vue-eslint-parser"]); const [pluginVue, parserVue, pluginTs] = await Promise.all([ interopDefault(import("eslint-plugin-vue")), interopDefault(import("vue-eslint-parser")), interopDefault(import("@typescript-eslint/eslint-plugin")) ]); const tsRules = await createTypescriptRules(); const configKeys = version === 3 ? ["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 [ { plugins: { vue: pluginVue } }, { files, languageOptions: { parser: parserVue, parserOptions: { ecmaFeatures: { jsx: true }, extraFileExtensions: [".vue"], parser: "@typescript-eslint/parser", sourceType: "module" } }, processor: pluginVue.processors[".vue"], plugins: { "@typescript-eslint": pluginTs }, rules: { ...tsRules, ...pluginVue.configs.base.rules, ...vueRules, "vue/block-order": ["warn", { order: ["template", "script", "style"] }], "vue/component-api-style": ["warn", ["script-setup", "composition"]], "vue/component-name-in-template-casing": [ "warn", "PascalCase", { registeredComponentsOnly: false, ignores: [] } ], "vue/component-options-name-casing": ["warn", "PascalCase"], "vue/custom-event-name-casing": ["warn", "camelCase"], "vue/define-emits-declaration": ["warn", "type-based"], "vue/define-macros-order": "off", "vue/define-props-declaration": ["warn", "type-based"], "vue/html-comment-content-newline": "warn", "vue/multi-word-component-names": "warn", "vue/next-tick-style": ["warn", "promise"], "vue/no-duplicate-attr-inheritance": "warn", "vue/no-required-prop-with-default": "warn", "vue/no-static-inline-styles": "warn", "vue/no-template-target-blank": "error", "vue/no-this-in-before-route-enter": "error", "vue/no-undef-properties": "warn", "vue/no-unsupported-features": "warn", "vue/no-unused-emit-declarations": "warn", "vue/no-unused-properties": "warn", "vue/no-unused-refs": "warn", "vue/no-use-v-else-with-v-for": "error", "vue/no-useless-mustaches": "warn", "vue/no-useless-v-bind": "error", "vue/no-v-text": "warn", "vue/padding-line-between-blocks": "warn", "vue/prefer-define-options": "warn", "vue/prefer-separate-static-class": "warn", "vue/prop-name-casing": ["warn", "camelCase"], "vue/require-macro-variable-name": [ "warn", { defineProps: "props", defineEmits: "emit", defineSlots: "slots", useSlots: "slots", useAttrs: "attrs" } ], "vue/valid-define-options": "warn", ...overrides } } ]; } // src/utils/options.ts import process2 from "node:process"; async function createOptions(options = {}) { const opts = { cwd: process2.cwd(), ignores: GLOB_EXCLUDE, gitignore: true, overrides: {}, prettierRules: { ...DEFAULT_PRETTIER_RULES }, usePrettierrc: true, formatter: { html: true, css: true } }; const { cwd, ignores, gitignore, overrides, prettierRules: prettierRules2, usePrettierrc, formatter, unocss, ...rest } = options; if (cwd) { opts.cwd = cwd; } if (ignores?.length) { opts.ignores = [...opts.ignores, ...ignores]; } if (gitignore) { opts.gitignore = gitignore; } if (overrides) { opts.overrides = overrides; } if (prettierRules2) { opts.prettierRules = { ...opts.prettierRules, ...prettierRules2 }; } if (usePrettierrc !== void 0) { opts.usePrettierrc = usePrettierrc; } if (opts.usePrettierrc) { const prettierConfig = await loadPrettierConfig(opts.cwd); Object.assign(opts.prettierRules, prettierConfig); } if (formatter) { Object.assign(opts.formatter, formatter); } const onDemandKeys = [ "vue", "react", "react-native", "solid", "svelte", "astro" ]; const onDemandFiles = { vue: [GLOB_VUE], react: [GLOB_JSX, GLOB_TSX], "react-native": [GLOB_JSX, GLOB_TSX], solid: [GLOB_JSX, GLOB_TSX], svelte: [GLOB_SVELTE], astro: [GLOB_ASTRO] }; onDemandKeys.forEach((key) => { if (key === "vue") { opts[key] = createItemDemandOptions( key, rest[key], onDemandFiles[key] ); } else { opts[key] = createItemDemandOptions(key, rest[key], onDemandFiles[key]); } }); if (rest["react-native"] && !rest.react) { opts.react = createItemDemandOptions("react", true, onDemandFiles.react); } opts.unocss = Boolean(unocss); return opts; } function createItemDemandOptions(key, options, files) { if (!options) return void 0; if (key === "vue") { const vueOptions = { version: 3, files }; if (typeof options === "object") { Object.assign(vueOptions, options); } return vueOptions; } const itemOptions = { files }; if (typeof options === "object") { Object.assign(itemOptions, options); } return itemOptions; } // src/index.ts async function defineConfig(options = {}, ...userConfigs) { const { gitignore: enableGitignore = true, usePrettierrc: enablePrettier = true, astro: enableAstro = false, react: enableReact = false, "react-native": enableReactNative = false, solid: enableSolid = false, svelte: enableSvelte = false, unocss: enableUnoCSS = false, vue: enableVue = false } = options; const opts = await createOptions(options); const overrideRecord = getOverridesRules(opts.overrides); const configs = []; if (enableGitignore) { configs.push(createGitIgnoresConfig(opts.gitignore)); } configs.push( createIgnoresConfig(opts.ignores), createJavascriptConfig(overrideRecord.js), createNodeConfig(overrideRecord.n), createJsdocConfig(overrideRecord.jsdoc), createImportsConfig(overrideRecord.import), createPerfectionistConfig(overrideRecord.perfectionist), createUnicornConfig(overrideRecord.unicorn), createTypescriptConfig(overrideRecord.ts) ); if (enableVue) { configs.push(createVueConfig(opts.vue, overrideRecord.vue)); } if (enableReact) { configs.push(createReactConfig(opts.react, overrideRecord.react)); } if (enableReactNative) { configs.push(createReactNativeConfig(opts["react-native"], overrideRecord["react-native"])); } if (enableSolid) { configs.push(createSolidConfig(opts.solid, overrideRecord.solid)); } if (enableSvelte) { configs.push(createSvelteConfig(opts.svelte, opts.prettierRules, overrideRecord.svelte)); } if (enableUnoCSS) { configs.push(createUnoCssConfig(opts.unocss, overrideRecord.unocss)); } if (enableAstro) { configs.push(createAstroConfig(opts.astro, opts.prettierRules, overrideRecord.astro)); } configs.push( createJsoncConfig(overrideRecord.jsonc), createSortPackageJsonConfig(), createSortTsConfig() ); configs.push(createDisablesConfig()); if (enablePrettier) { configs.push(createPrettierConfig(opts.prettierRules)); } configs.push(createFormatterConfig(opts.formatter, opts.prettierRules)); let composer = new FlatConfigComposer(); composer = composer.append(...configs, ...userConfigs); return composer; } export { defineConfig };