UNPKG

@rebeccastevens/eslint-config

Version:
1,288 lines (1,279 loc) 168 kB
import * as path from 'node:path'; import path__default from 'node:path'; import { isPackageExists } from 'local-pkg'; import fs from 'node:fs/promises'; import globals from 'globals'; async function interopDefault(value) { const resolved = await value; // eslint-disable-next-line ts/no-unsafe-return, ts/no-explicit-any, ts/no-unsafe-member-access return resolved.default ?? resolved; } const parserPlain = { meta: { name: "parser-plain", }, parseForESLint: (code) => ({ ast: { body: [], comments: [], loc: { end: code.length, start: 0 }, range: [0, code.length], tokens: [], type: "Program", }, scopeManager: null, services: { isPlain: true }, visitorKeys: { Program: [], }, }), }; async function loadPackages(packageIds) { const missing = packageIds.filter((id) => !isPackageExists(id)); if (missing.length > 0) { await installPackages(missing); } // eslint-disable-next-line ts/no-explicit-any, ts/no-unsafe-return return Promise.all(packageIds.map((id) => interopDefault(import(id)))); } const mut_installPackagesToLoad = new Set(); let mut_installPackagesAction = null; let mut_installPackagesActionResolver = null; let mut_installPackagesTimeout = null; /* eslint-disable functional/no-loop-statements */ async function installPackages(packages) { for (const p of packages) { mut_installPackagesToLoad.add(p); } if (mut_installPackagesTimeout !== null) { clearTimeout(mut_installPackagesTimeout); } mut_installPackagesTimeout = setTimeout(() => { const allPackages = [...mut_installPackagesToLoad.values()]; mut_installPackagesTimeout = null; mut_installPackagesToLoad.clear(); mut_installPackagesAction = null; mut_installPackagesActionResolver(allPackages); mut_installPackagesActionResolver = null; }, 100); mut_installPackagesAction ??= new Promise((resolve) => { mut_installPackagesActionResolver = resolve; }).then(async (allPackages) => { const allPackagesString = allPackages.join(", "); if (Boolean(process.env["CI"]) || !process.stdout.isTTY) { throw new Error(`Missing packages: ${allPackagesString}`); } const prompt = await import('@clack/prompts'); const result = await prompt.confirm({ message: allPackages.length === 1 ? `${allPackages[0]} is required for this config. Do you want to install it?` : `Packages are required for this config: ${allPackagesString}.\nDo you want to install them?`, }); if (result !== false) { await import('@antfu/install-pkg').then(({ installPackage }) => installPackage(allPackages, { dev: true })); } }); return mut_installPackagesAction; } /* eslint-enable functional/no-loop-statements */ async function comments() { const [pluginComments] = (await loadPackages(["@eslint-community/eslint-plugin-eslint-comments"])); return [ { name: "rs:comments", 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", "eslint-comments/disable-enable-pair": ["error", { allowWholeFile: true }], }, }, ]; } const GLOB_SRC_EXT = "?([cm])[jt]s?(x)"; const GLOB_SRC = "**/?(.)*.?([cm])[jt]s?(x)"; const GLOB_JS = "**/?(.)*.?([cm])js"; const GLOB_JSX = "**/?(.)*.?([cm])jsx"; const GLOB_TS = "**/?(.)*.?([cm])ts"; const GLOB_TSX = "**/?(.)*.?([cm])tsx"; const GLOB_DTS = "**/?(.)*.d.?([cm])ts"; const GLOB_ROOT_JS = "./?(.)*.?([cm])js"; const GLOB_ROOT_JSX = "./?(.)*.?([cm])jsx"; const GLOB_ROOT_TS = "./?(.)*.?([cm])ts"; const GLOB_ROOT_TSX = "./?(.)*.?([cm])tsx"; const GLOB_ROOT_DTS = "./?(.)*.d.?([cm])ts"; const GLOB_TYPINGS = "typings/**/?(.)*.?([cm])ts"; const GLOB_MJS = "**/?(.)*.mjs"; const GLOB_MTS = "**/?(.)*.mts"; const GLOB_CJS = "**/?(.)*.cjs"; const GLOB_CTS = "**/?(.)*.cts"; // cspell:disable-next-line const GLOB_STYLE = "**/?(.)*.{c,le,sc,pc,postc}ss"; const GLOB_CSS = "**/?(.)*.css"; const GLOB_POSTCSS = "**/?(.)*.{p,post}css"; const GLOB_LESS = "**/?(.)*.less"; const GLOB_SCSS = "**/?(.)*.scss"; const GLOB_JSON = "**/?(.)*.json"; const GLOB_JSON5 = "**/?(.)*.json5"; const GLOB_JSONC = "**/?(.)*.jsonc"; const GLOB_MARKDOWN = "**/?(.)*.md"; const GLOB_MARKDOWN_IN_MARKDOWN = "**/?(.)*.md/?(.)*.md"; const GLOB_VUE = "**/?(.)*.vue"; const GLOB_YAML = "**/?(.)*.y?(a)ml"; const GLOB_TOML = "**/?(.)*.toml"; const GLOB_HTML = "**/?(.)*.htm?(l)"; const GLOB_GRAPHQL = "**/?(.)*.{g,graph}ql"; 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_ALL_SRC = [ GLOB_SRC, GLOB_STYLE, GLOB_JSON, GLOB_JSON5, GLOB_JSONC, GLOB_MARKDOWN, GLOB_VUE, GLOB_YAML, GLOB_HTML, GLOB_TOML, GLOB_GRAPHQL, ]; const GLOB_EXCLUDE = [ "**/node_modules", "**/dist", "**/lib", "**/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", "**/.yarn", "**/CHANGELOG*.md", "**/?(.)*.min.*", "**/LICENSE*", "**/__snapshots__", "**/auto-import.ts", "**/auto-import.d.ts", "**/auto-imports.ts", "**/auto-imports.d.ts", "**/components.ts", "**/components.d.ts", "**/typegen.ts", "**/typegen.d.ts", ]; const StylisticConfigDefaults = { indent: 2, jsx: true, quotes: "double", semi: true, printWidth: 120, }; async function stylistic(options) { const { stylistic: { indent, jsx, quotes, semi }, overrides, typescript, } = options; const [pluginStylistic] = (await loadPackages(["@stylistic/eslint-plugin"])); const config = pluginStylistic.configs.customize({ flat: true, indent, jsx, pluginName: "@stylistic", quotes, semi, }); return [ { name: "rs:stylistic", plugins: { "@stylistic": pluginStylistic, }, rules: { ...config.rules, "@stylistic/array-bracket-spacing": ["error", "never"], "@stylistic/arrow-parens": ["error", "always"], "@stylistic/arrow-spacing": ["error", { before: true, after: true }], "@stylistic/block-spacing": ["error", "always"], "@stylistic/brace-style": "error", "@stylistic/comma-dangle": [ "error", { arrays: "only-multiline", exports: "only-multiline", functions: "ignore", imports: "only-multiline", objects: "only-multiline", ...(typescript ? { enums: "only-multiline", generics: "only-multiline", tuples: "only-multiline", } : {}), }, ], "@stylistic/comma-spacing": ["error", { before: false, after: true }], "@stylistic/comma-style": ["error", "last"], "@stylistic/computed-property-spacing": "error", "@stylistic/dot-location": ["error", "property"], "@stylistic/eol-last": "error", "@stylistic/function-call-spacing": ["error", "never"], "@stylistic/generator-star-spacing": ["error", "after"], "@stylistic/indent": typescript ? "off" : [ "error", indent, { SwitchCase: 1, VariableDeclarator: 1, outerIIFEBody: 1, MemberExpression: 1, FunctionDeclaration: { parameters: 1, body: 1 }, FunctionExpression: { parameters: 1, body: 1 }, CallExpression: { arguments: 1 }, ArrayExpression: 1, ObjectExpression: 1, ImportDeclaration: 1, flatTernaryExpressions: false, ignoreComments: false, }, ], "@stylistic/indent-binary-ops": "error", "@stylistic/key-spacing": ["error", { beforeColon: false, afterColon: true }], "@stylistic/keyword-spacing": ["error", { before: true, after: true }], "@stylistic/linebreak-style": ["error", "unix"], "@stylistic/lines-around-comment": [ "warn", { beforeBlockComment: true, beforeLineComment: false, afterBlockComment: false, afterLineComment: false, afterHashbangComment: true, allowBlockStart: true, allowBlockEnd: true, allowObjectStart: true, allowObjectEnd: true, allowArrayStart: true, allowArrayEnd: true, allowClassStart: true, allowClassEnd: true, ...(typescript ? { allowEnumEnd: true, allowEnumStart: true, allowInterfaceEnd: true, allowInterfaceStart: true, allowModuleEnd: true, allowModuleStart: true, allowTypeEnd: true, allowTypeStart: true, } : {}), }, ], "@stylistic/lines-between-class-members": [ "error", "always", { exceptAfterSingleLine: true, ...(typescript ? { exceptAfterOverload: true, } : {}), }, ], "@stylistic/max-statements-per-line": ["error", { max: 1 }], "@stylistic/multiline-ternary": ["error", "always-multiline"], "@stylistic/new-parens": "error", "@stylistic/newline-per-chained-call": ["error", { ignoreChainWithDepth: 2 }], "@stylistic/no-extra-parens": ["error", "all", { nestedBinaryExpressions: false }], "@stylistic/no-extra-semi": "error", "@stylistic/no-floating-decimal": "error", "@stylistic/no-mixed-operators": [ "error", { groups: [ ["+", "-", "*", "/", "%", "**"], ["&", "|", "^", "~", "<<", ">>", ">>>"], ["==", "!=", "===", "!==", ">", ">=", "<", "<="], ["&&", "||"], ["in", "instanceof"], ], allowSamePrecedence: true, }, ], "@stylistic/no-mixed-spaces-and-tabs": "error", "@stylistic/no-multi-spaces": ["error", { ignoreEOLComments: true }], "@stylistic/no-multiple-empty-lines": ["error", { max: 1, maxEOF: 1 }], "@stylistic/no-tabs": "error", "@stylistic/no-trailing-spaces": "error", "@stylistic/no-whitespace-before-property": "error", "@stylistic/nonblock-statement-body-position": ["error", "beside", { overrides: {} }], "@stylistic/object-curly-newline": [ "error", { ObjectExpression: { minProperties: 3, multiline: true, consistent: true, }, ObjectPattern: { minProperties: 3, multiline: true, consistent: true, }, }, ], "@stylistic/object-curly-spacing": ["error", "always"], "@stylistic/object-property-newline": ["error", { allowAllPropertiesOnSameLine: true }], "@stylistic/one-var-declaration-per-line": ["error", "always"], "@stylistic/operator-linebreak": [ "error", "after", { overrides: { // "=": "none", "==": "none", "===": "none", "?": "before", ":": "before", "|": "before", }, }, ], "@stylistic/padded-blocks": [ "error", { blocks: "never", switches: "never", classes: "never", }, ], "@stylistic/quote-props": ["error", "consistent-as-needed"], "@stylistic/quotes": ["error", quotes, { avoidEscape: true, allowTemplateLiterals: "never" }], "@stylistic/rest-spread-spacing": ["error", "never"], "@stylistic/semi-spacing": ["error", { before: false, after: true }], "@stylistic/semi-style": ["error", "last"], "@stylistic/semi": ["error", semi ? "always" : "never"], "@stylistic/space-before-blocks": ["error", "always"], "@stylistic/space-before-function-paren": [ "error", { asyncArrow: "always", anonymous: "never", named: "never", }, ], "@stylistic/space-in-parens": ["error", "never"], "@stylistic/space-infix-ops": "error", "@stylistic/space-unary-ops": ["error", { words: true, nonwords: false }], "@stylistic/spaced-comment": [ "error", "always", { line: { exceptions: ["-", "+", "*"], markers: ["*package", "!", "/", ",", "="], }, block: { balanced: true, exceptions: ["-", "+", "*"], markers: ["*package", "!", "*", ",", ":", "::", "flow-include"], }, }, ], "@stylistic/switch-colon-spacing": ["error", { after: true, before: false }], "@stylistic/template-curly-spacing": ["error", "never"], "@stylistic/template-tag-spacing": ["error", "never"], "@stylistic/wrap-iife": ["error", "inside", { functionPrototypeMethods: true }], "@stylistic/yield-star-spacing": ["error", "after"], ...(typescript ? { "@stylistic/member-delimiter-style": "error", "@stylistic/type-annotation-spacing": "error", } : {}), ...overrides, }, }, ]; } async function formatters(opts, stylistic) { const options = opts === true ? { js: true, ts: true, dts: false, json: true, yaml: true, css: true, graphql: true, html: true, markdown: true, slidev: isPackageExists("@slidev/cli"), tailwind: isPackageExists("tailwindcss"), } : opts; if (options.slidev !== false && options.slidev !== undefined && options.markdown !== true) { throw new Error("`slidev` option only works when `markdown` is enabled with `prettier`"); } const { indent, printWidth, quotes, semi } = stylistic; const prettierOptions = Object.assign({ endOfLine: "lf", printWidth: printWidth ?? 120, semi: semi ?? true, singleQuote: quotes === "single", tabWidth: typeof indent === "number" ? indent : typeof StylisticConfigDefaults.indent === "number" ? StylisticConfigDefaults.indent : 2, trailingComma: "all", useTabs: indent === "tab", }, options.prettierOptions ?? {}); const packages = loadPackages([ "eslint-plugin-format", "eslint-config-prettier", "sort-package-json", "eslint-formatting-reporter", "prettier", ]); const [pluginFormat, configPrettier, sortPackageJson, formattingReporter] = await packages; const turnOffRulesForPrettier = { ...Object.fromEntries(Object.entries(configPrettier.rules ?? {}).filter(([, value]) => value === "off")), // curly: "off", "no-unexpected-multiline": "off", // "@stylistic/lines-around-comment": "off", "@stylistic/max-len": "off", "@stylistic/no-confusing-arrow": "off", "@stylistic/no-mixed-operators": "off", "@stylistic/no-tabs": "off", "@stylistic/quotes": "off", // "@stylistic/js/lines-around-comment": "off", // "@stylistic/js/max-len": "off", "@stylistic/js/no-confusing-arrow": "off", "@stylistic/js/no-mixed-operators": "off", "@stylistic/js/no-tabs": "off", "@stylistic/js/quotes": "off", // "@stylistic/ts/lines-around-comment": "off", "@stylistic/ts/quotes": "off", "@typescript-eslint/lines-around-comment": "off", "@typescript-eslint/quotes": "off", // "babel/quotes": "off", // "unicorn/template-indent": "off", "vue/html-self-closing": "off", "vue/max-len": "off", // other "no-irregular-whitespace": "off", "yml/block-sequence-hyphen-indicator-newline": "off", }; const mut_configs = [ { name: "rs:formatters:setup", plugins: { format: pluginFormat, }, }, ]; if (options.js !== undefined && options.js) { mut_configs.push({ name: "rs:formatter:javascript", files: [GLOB_JS, GLOB_JSX], rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "babel", ...(options.tailwind !== undefined && options.tailwind ? { plugins: prettierOptions.plugins ?? [], } : {}), }, ], }, }); } if (options.ts !== undefined && options.ts) { mut_configs.push({ name: "rs:formatter:typescript", files: [GLOB_TS, GLOB_TSX], ignores: options.dts === true ? [] : [GLOB_DTS], rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "typescript", ...(options.tailwind !== undefined && options.tailwind ? { plugins: prettierOptions.plugins ?? [], } : {}), }, ], }, }); } if (options.yaml !== undefined && options.yaml) { mut_configs.push({ name: "rs:formatter:yaml", files: [GLOB_YAML], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "yaml", }, ], }, }); } if (options.json !== undefined && options.json) { mut_configs.push({ name: "rs:formatter:json", files: [GLOB_JSON], ignores: ["**/package.json"], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "json", }, ], }, }, { name: "rs:formatter:jsonc", files: [GLOB_JSONC], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "jsonc", }, ], }, }, { name: "rs:formatter:json5", files: [GLOB_JSON5], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "json5", }, ], }, }, { name: "rs:formatter:packagejson", files: ["**/package.json"], languageOptions: { parser: parserPlain, }, plugins: { "package-json": { meta: { name: "rs:formatter:packagejson", }, rules: { sort: { meta: { type: "layout", fixable: "whitespace", messages: formattingReporter.messages, schema: [ { type: "object", properties: { parser: { type: "string", required: true, }, }, additionalProperties: true, }, ], }, create(context) { return { Program() { if (!("text" in context.sourceCode)) { return; } const sourceCode = context.sourceCode.text; try { const formatted = sortPackageJson(sourceCode); formattingReporter.reportDifferences(context, sourceCode, formatted); } catch (error) { console.error(error); context.report({ loc: { start: { line: 1, column: 0 }, end: { line: 1, column: 0 }, }, message: "Failed to format package.json", }); } }, }; }, }, }, }, }, rules: { "package-json/sort": "error", }, }); } if (options.css !== undefined && options.css) { mut_configs.push({ name: "rs:formatter:css", files: [GLOB_CSS, GLOB_POSTCSS], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "css", }, ], }, }, { name: "rs:formatter:scss", files: [GLOB_SCSS], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "scss", }, ], }, }, { name: "rs:formatter:less", files: [GLOB_LESS], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "less", }, ], }, }); } if (options.html !== undefined && options.html) { mut_configs.push({ name: "rs:formatter:html", files: ["**/*.html"], languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "html", }, ], }, }); } if (options.markdown !== undefined && options.markdown) { const GLOB_SLIDEV = options.slidev === undefined || options.slidev === false ? [] : options.slidev === true ? ["**/slides.md"] : (options.slidev.files ?? []); mut_configs.push({ name: "rs:formatter:markdown", files: [GLOB_MARKDOWN], ignores: GLOB_SLIDEV, languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, embeddedLanguageFormatting: "off", parser: "markdown", }, ], }, }); if (options.slidev !== undefined && options.slidev !== false) { mut_configs.push({ name: "rs:formatter:slidev", files: GLOB_SLIDEV, languageOptions: { parser: parserPlain, }, rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, embeddedLanguageFormatting: "off", parser: "slidev", plugins: ["prettier-plugin-slidev"], }, ], }, }); } } if (options.graphql !== undefined && options.graphql) { mut_configs.push({ files: [GLOB_GRAPHQL], languageOptions: { parser: parserPlain, }, name: "rs:formatter:graphql", rules: { ...turnOffRulesForPrettier, "format/prettier": [ "error", { ...prettierOptions, parser: "graphql", }, ], }, }); } return mut_configs; } async function functional(options) { const { mode, overrides, stylistic, filesTypeAware, functionalEnforcement, ignoreNamePattern, // ignoreTypePattern, } = options; const [pluginFunctional] = (await loadPackages(["eslint-plugin-functional"])); const strictRules = { "functional/functional-parameters": "error", "functional/no-throw-statements": "error", "functional/no-try-statements": "error", "functional/no-let": "error", "functional/no-class-inheritance": "error", "functional/no-this-expressions": "error", "functional/no-loop-statements": "error", "functional/immutable-data": "error", "functional/prefer-immutable-types": "error", "functional/type-declaration-immutability": "error", "functional/no-mixed-types": "error", "functional/no-conditional-statements": [ "error", { ignoreCodePattern: ["import.meta.vitest"], }, ], "functional/no-expression-statements": "error", "functional/no-return-void": "error", "functional/prefer-property-signatures": stylistic === false ? "off" : "error", "functional/prefer-tacit": stylistic === false ? "off" : "warn", "functional/readonly-type": stylistic === false ? "off" : "error", }; const recommendedRules = { "functional/functional-parameters": [ "error", { allowRestParameter: true, enforceParameterCount: false, }, ], "functional/no-let": [ "error", { allowInForLoopInit: true, ignoreIdentifierPattern: ignoreNamePattern, }, ], "functional/no-loop-statements": "error", "functional/no-throw-statements": [ "error", { allowToRejectPromises: true, }, ], "functional/immutable-data": [ "error", { ignoreAccessorPattern: ["**.mut_*.**"], ignoreClasses: "fieldsOnly", ignoreImmediateMutation: true, ignoreNonConstDeclarations: true, }, ], "functional/no-conditional-statements": [ "error", { allowReturningBranches: true, ignoreCodePattern: ["import.meta.vitest"], }, ], "functional/no-expression-statements": [ "error", { ignoreCodePattern: "^assert", ignoreSelfReturning: true, ignoreVoid: true, }, ], "functional/no-return-void": mode === "library" ? "error" : "off", "@typescript-eslint/prefer-readonly-parameter-types": "off", "functional/prefer-immutable-types": [ mode === "library" ? "warn" : "off", { enforcement: "None", overrides: [ { specifiers: [ { from: "file", }, { from: "lib", }, ], options: { ignoreInferredTypes: true, ignoreNamePattern, parameters: { enforcement: "ReadonlyShallow" }, }, }, ], suggestions: { ReadonlyShallow: [ [ { pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$", replace: "readonly $1", message: "Prepend with readonly.", }, { pattern: "^(Array|Map|Set)<(.+)>$", replace: "Readonly$1<$2>", message: "Use Readonly$1 instead of $1.", }, ], [ { pattern: "^(.+)$", replace: "Readonly<$1>", message: "Surround with Readonly.", }, ], ], ReadonlyDeep: [ [ { pattern: "^(?:Readonly<(.+)>|(.+))$", replace: "ReadonlyDeep<$1$2>", message: "Surround with ReadonlyDeep.", }, ], ], Immutable: [ [ { pattern: "^(?:Readonly(?:Deep)?<(.+)>|(.+))$", replace: "Immutable<$1$2>", message: "Surround with Immutable.", }, ], ], }, }, ], "functional/type-declaration-immutability": [ "error", { rules: [ { identifiers: "I?Immutable.+", immutability: "Immutable", comparator: "AtLeast", }, { identifiers: "I?ReadonlyDeep.+", immutability: "ReadonlyDeep", comparator: "AtLeast", }, { identifiers: "I?Readonly.+", immutability: "ReadonlyShallow", comparator: "AtLeast", fixer: [ { pattern: "^([_$a-zA-Z\\xA0-\\uFFFF][_$a-zA-Z0-9\\xA0-\\uFFFF]*\\[\\])$", replace: "readonly $1", }, { pattern: "^(Array|Map|Set)<(.+)>$", replace: "Readonly$1<$2>", }, { pattern: "^(.+)$", replace: "Readonly<$1>", }, ], }, { identifiers: "I?Mutable.+", immutability: "Mutable", comparator: "AtMost", fixer: [ { pattern: "^Readonly(Array|Map|Set)<(.+)>$", replace: "$1<$2>", }, { pattern: "^Readonly<(.+)>$", replace: "$1", }, ], }, ], }, ], "functional/prefer-property-signatures": stylistic === false ? "off" : "error", "functional/prefer-tacit": stylistic === false ? "off" : "warn", "functional/readonly-type": stylistic === false ? "off" : "error", }; const liteRules = { ...recommendedRules, "functional/no-conditional-statements": "off", "functional/no-expression-statements": "off", "functional/no-return-void": "off", "functional/prefer-immutable-types": [ mode === "library" ? "warn" : "off", { ...recommendedRules["functional/prefer-immutable-types"][1], overrides: [ { ...recommendedRules["functional/prefer-immutable-types"][1].overrides[0], specifiers: [ { from: "file", }, ], }, ], }, ], }; const noneLibraryRules = { "functional/prefer-immutable-types": liteRules["functional/prefer-immutable-types"], "functional/type-declaration-immutability": ["warn", liteRules["functional/type-declaration-immutability"][1]], }; return [ { name: "rs:functional", plugins: { functional: pluginFunctional, }, settings: { immutability: { overrides: [ { type: "^Readonly<.+>$", to: "ReadonlyShallow" }, { type: "^ReadonlyDeep<.+>$", to: "ReadonlyDeep" }, { type: "^Immutable<.+>$", to: "Immutable" }, ], }, }, rules: { ...pluginFunctional.configs.off.rules, ...(functionalEnforcement === "none" ? mode === "library" ? noneLibraryRules : {} : functionalEnforcement === "lite" ? liteRules : functionalEnforcement === "strict" ? strictRules : recommendedRules), ...overrides, }, }, { name: "rs:functional:disable-type-aware", ignores: filesTypeAware, rules: pluginFunctional.configs.disableTypeChecked.rules, }, ]; } async function ignores(options) { const { ignoreFiles, ignores: ignoresOptions, projectRoot } = options; const includeIgnoreFile = ignoreFiles.length === 0 ? undefined : await loadPackages(["@eslint/compat"]).then(([p]) => p.includeIgnoreFile); const [extend, files] = Array.isArray(ignoresOptions) ? [true, ignoresOptions] : [ignoresOptions.extend, ignoresOptions.files]; const ignoreConfig = { ignores: extend ? [...GLOB_EXCLUDE, ...files] : [...files], }; const ignoreFileConfigs = await Promise.all(ignoreFiles.map((file) => { const filePath = path__default.resolve(projectRoot, file); return fs .access(filePath) .then(() => includeIgnoreFile(filePath)) .catch(() => { console.warn(`Ignore file "${filePath}" not found.`); return null; }); })); return [ignoreConfig, ...ignoreFileConfigs.filter((v) => v !== null)]; } async function imports(options) { const { stylistic, parserOptions, typescript } = options; const [pluginImport] = (await loadPackages(typescript ? [ "eslint-plugin-import-x", "eslint-import-resolver-typescript", // make sure it exists - we only implicitly use it ] : ["eslint-plugin-import-x"])); return [ { name: "rs:imports", plugins: { "import-x": pluginImport, }, settings: { "import-x/external-module-folders": ["node_modules", "node_modules/@types"], "import-x/internal-regex": "^(?:#|(?:@|~)\\/).*", "import-x/extensions": [".ts", ".tsx", ".js", ".jsx"], "import-x/parsers": { ...(typescript ? { "@typescript-eslint/parser": [".ts", ".tsx", ".cts", ".mts"], } : undefined), }, "import-x/resolver": { node: { extensions: [".ts", ".tsx", ".js", ".jsx"], }, ...(typescript ? { typescript: { alwaysTryTypes: true, projectService: parserOptions.projectService, }, } : undefined), }, }, rules: { // "import-x/consistent-type-specifier-style": ["error", "prefer-inline"], -- using ts/consistent-type-imports instead "import-x/default": "error", // "import-x/dynamic-import-chunkname": "off", "import-x/export": "error", // "import-x/exports-last": "off", // "import-x/extensions": "off", "import-x/first": "error", // "import-x/group-exports": "off", // "import-x/max-dependencies": [ // "off", // { // max: 10, // }, // ], "import-x/named": "error", "import-x/namespace": [ "error", { allowComputed: true, }, ], "import-x/no-absolute-path": "error", "import-x/no-amd": "error", // "import-x/no-anonymous-default-export": "off", // "import-x/no-commonjs": "off", // "import-x/no-cycle": "off", // "import-x/no-default-export": "off", // "import-x/no-deprecated": "warn", "import-x/no-duplicates": ["error", { "prefer-inline": true }], // "import-x/no-dynamic-require": "off", "import-x/no-empty-named-blocks": "error", // "import-x/no-internal-modules": "off", "import-x/no-mutable-exports": "error", // "import-x/no-named-as-default": "off", // "import-x/no-named-as-default-member": "off", "import-x/no-named-default": "error", // "import-x/no-named-export": "off", // "import-x/no-namespace": "off", // "import-x/no-nodejs-modules": "off", // "import-x/no-relative-parent-imports": "off", // "import-x/no-restricted-paths": "off", "import-x/no-self-import": "error", "import-x/no-unassigned-import": "error", // "import-x/no-unused-modules": "off", // "import-x/no-unresolved": "off", "import-x/no-useless-path-segments": [ "error", { commonjs: true, noUselessIndex: true, }, ], "import-x/no-webpack-loader-syntax": "error", // "import-x/prefer-default-export": "off", // "import-x/unambiguous": "off", "import-x/newline-after-import": [stylistic === false ? "off" : "error", { count: 1 }], "import-x/order": [ stylistic === false ? "off" : "error", { alphabetize: { caseInsensitive: false, order: "asc", }, groups: ["builtin", "external", "internal", "parent", "sibling", "index"], "newlines-between": "always", }, ], }, }, { files: [GLOB_MJS, GLOB_MTS], rules: { "import-x/no-commonjs": "error", "import-x/no-dynamic-require": "error", }, }, ...(typescript ? [ { files: [GLOB_TS, GLOB_TSX, GLOB_DTS], rules: { "import-x/no-unresolved": "off", "import-x/named": "off", "import-x/default": "off", "import-x/namespace": "off", "@typescript-eslint/no-import-type-side-effects": "error", "@typescript-eslint/consistent-type-imports": [ stylistic === false ? "off" : "error", { prefer: "type-imports", fixStyle: "inline-type-imports", disallowTypeAnnotations: false, }, ], }, }, ] : []), ...(options.mode === "library" ? [ { files: [GLOB_SRC], rules: { // Use non-sloppy imports. See: https://jsr.io/docs/publishing-packages#relative-imports "import-x/extensions": [ "error", "always", { checkTypeImports: true, ignorePackages: true, }, ], "import-x/no-useless-path-segments": ["error", { noUselessIndex: false }], "import-x/no-extraneous-dependencies": [ "error", { bundledDependencies: true, devDependencies: [ `?(.)*.${GLOB_SRC_EXT}`, `?(.)config?(s)/**`, "**/?(.)script?(s)/**", "**/spec/**", "**/test?(s)/**", "**/__mocks__/**", "**/__tests__/**", `**/*{.,_}{test,spec}.${GLOB_SRC_EXT}`, `**/Gruntfile{,.${GLOB_SRC_EXT}`, `**/gulpfile.${GLOB_SRC_EXT}`, `**/gulpfile.*.${GLOB_SRC_EXT}`, `**/jest.config.${GLOB_SRC_EXT}`, `**/jest.setup.${GLOB_SRC_EXT}`, `**/karma.conf.${GLOB_SRC_EXT}`, `**/postcss.config.${GLOB_SRC_EXT}`, `**/protractor.conf.${GLOB_SRC_EXT}`, `**/protractor.conf.*.${GLOB_SRC_EXT}`, `**/rollup.config.${GLOB_SRC_EXT}`, `**/rollup.config.*.${GLOB_SRC_EXT}`, `**/tailwind.config.${GLOB_SRC_EXT}`, `**/tailwind.config.*.${GLOB_SRC_EXT}`, `**/vite.config.${GLOB_SRC_EXT}`, `**/vitest.config.${GLOB_SRC_EXT}`, `**/vue.config.${GLOB_SRC_EXT}`, `**/webpack.config.${GLOB_SRC_EXT}`,