UNPKG

@tpointurier/configs

Version:

ESLint config presets

1,444 lines (1,421 loc) 43.9 kB
// src/eslint/index.ts import { existsSync } from "node:fs"; // node_modules/.pnpm/eslint-flat-config-utils@2.0.1/node_modules/eslint-flat-config-utils/dist/index.mjs function hijackPluginRule(plugin, name, factory) { const original = plugin.rules?.[name]; if (!original) { throw new Error(`Rule "${name}" not found in plugin "${plugin.meta?.name || plugin.name}"`); } const patched = factory(original); if (patched !== plugin.rules[name]) plugin.rules[name] = patched; return plugin; } var FLAG_DISABLE_FIXES = "__eslint-flat-config-utils-disable-fixes__"; function disableRuleFixes(rule) { if (rule[FLAG_DISABLE_FIXES]) { return rule; } const originalCreate = rule.create.bind(rule); rule.create = (context) => { const clonedContext = { ...context }; const proxiedContext = new Proxy(clonedContext, { get(target, prop, receiver) { if (prop === "report") { return function(report) { if (report.fix) { delete report.fix; } return Reflect.get(context, prop, receiver)({ ...report, fix: void 0 }); }; } return Reflect.get(context, prop, receiver); }, set(target, prop, value, receiver) { return Reflect.set(context, prop, value, receiver); } }); Object.defineProperty(proxiedContext, FLAG_DISABLE_FIXES, { value: true, enumerable: false }); return originalCreate(proxiedContext); }; return rule; } function mergeConfigs(...configs) { const keys = new Set(configs.flatMap((i) => Object.keys(i))); const merged = configs.reduce((acc, cur) => { return { ...acc, ...cur, files: [ ...acc.files || [], ...cur.files || [] ], ignores: [ ...acc.ignores || [], ...cur.ignores || [] ], plugins: { ...acc.plugins, ...cur.plugins }, rules: { ...acc.rules, ...cur.rules }, languageOptions: { ...acc.languageOptions, ...cur.languageOptions }, linterOptions: { ...acc.linterOptions, ...cur.linterOptions } }; }, {}); for (const key of Object.keys(merged)) { if (!keys.has(key)) delete merged[key]; } return merged; } function parseRuleId(ruleId) { let plugin; let rule = ruleId; if (ruleId.includes("/")) { if (ruleId.startsWith("@")) { plugin = ruleId.slice(0, ruleId.lastIndexOf("/")); } else { plugin = ruleId.slice(0, ruleId.indexOf("/")); } rule = ruleId.slice(plugin.length + 1); } else { plugin = null; rule = ruleId; } return { plugin, rule }; } function renamePluginsInRules(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]; }) ); } function renamePluginsInConfigs(configs, map) { return configs.map((i) => { const clone = { ...i }; if (clone.rules) clone.rules = renamePluginsInRules(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; }); } var DEFAULT_PLUGIN_CONFLICTS_ERROR = `Different instances of plugin "{{pluginName}}" found in multiple configs: {{configNames}}. It's likely you misconfigured the merge of these configs.`; var FlatConfigComposer = class _FlatConfigComposer extends Promise { _operations = []; _operationsOverrides = []; _operationsResolved = []; _renames = {}; _pluginsConflictsError = /* @__PURE__ */ new Map(); constructor(...configs) { super(() => { }); if (configs.length) this.append(...configs); } /** * Set plugin renames, like `n` -> `node`, `import-x` -> `import`, etc. * * This will runs after all config items are resolved. Applies to `plugins` and `rules`. */ renamePlugins(renames) { Object.assign(this._renames, renames); return this; } /** * Append configs to the end of the current configs array. */ append(...items) { const promise = Promise.all(items); this._operations.push(async (configs) => { const resolved = (await promise).flat().filter(Boolean); return [...configs, ...resolved]; }); return this; } /** * Prepend configs to the beginning of the current configs array. */ prepend(...items) { const promise = Promise.all(items); this._operations.push(async (configs) => { const resolved = (await promise).flat().filter(Boolean); return [...resolved, ...configs]; }); return this; } /** * Insert configs before a specific config. */ insertBefore(nameOrIndex, ...items) { const promise = Promise.all(items); this._operations.push(async (configs) => { const resolved = (await promise).flat().filter(Boolean); const index = getConfigIndex(configs, nameOrIndex); configs.splice(index, 0, ...resolved); return configs; }); return this; } /** * Insert configs after a specific config. */ insertAfter(nameOrIndex, ...items) { const promise = Promise.all(items); this._operations.push(async (configs) => { const resolved = (await promise).flat().filter(Boolean); const index = getConfigIndex(configs, nameOrIndex); configs.splice(index + 1, 0, ...resolved); return configs; }); return this; } /** * Provide overrides to a specific config. * * It will be merged with the original config, or provide a custom function to replace the config entirely. */ override(nameOrIndex, config) { this._operationsOverrides.push(async (configs) => { const index = getConfigIndex(configs, nameOrIndex); const extended = typeof config === "function" ? await config(configs[index]) : mergeConfigs(configs[index], config); configs.splice(index, 1, extended); return configs; }); return this; } /** * Provide overrides to multiple configs as an object map. * * Same as calling `override` multiple times. */ overrides(overrides) { for (const [name, config] of Object.entries(overrides)) { if (config) this.override(name, config); } return this; } /** * Override rules and it's options in **all configs**. * * Pass `null` as the value to remove the rule. * * @example * ```ts * composer * .overrideRules({ * 'no-console': 'off', * 'no-unused-vars': ['error', { vars: 'all', args: 'after-used' }], * // remove the rule from all configs * 'no-undef': null, * }) * ``` */ overrideRules(rules) { this._operationsOverrides.push(async (configs) => { for (const config of configs) { if (!("rules" in config) || !config.rules) continue; const configRules = config.rules; for (const [key, value] of Object.entries(rules)) { if (!(key in configRules)) continue; if (value == null) delete configRules[key]; else configRules[key] = value; } } return configs; }); return this; } /** * Remove rules from **all configs**. * * @example * ```ts * composer * .removeRules( * 'no-console', * 'no-unused-vars' * ) * ``` */ removeRules(...rules) { return this.overrideRules(Object.fromEntries( rules.map((rule) => [rule, null]) )); } /** * Remove a specific config by name or index. */ remove(nameOrIndex) { this._operations.push(async (configs) => { const index = getConfigIndex(configs, nameOrIndex); configs.splice(index, 1); return configs; }); return this; } /** * Replace a specific config by name or index. * * The original config will be removed and replaced with the new one. */ replace(nameOrIndex, ...items) { const promise = Promise.all(items); this._operations.push(async (configs) => { const resolved = (await promise).flat().filter(Boolean); const index = getConfigIndex(configs, nameOrIndex); configs.splice(index, 1, ...resolved); return configs; }); return this; } /** * Hijack into plugins to disable fixes for specific rules. * * Note this mutates the plugin object, use with caution. * * @example * ```ts * const config = await composer(...) * .disableRulesFix([ * 'unused-imports/no-unused-imports', * 'vitest/no-only-tests' * ]) * ``` */ disableRulesFix(ruleIds, options = {}) { this._operations.push(async (configs) => { for (const name of ruleIds) { const parsed = parseRuleId(name); if (!parsed.plugin) { if (!options.builtinRules) throw new Error(`Patching core rule "${name}" require pass \`{ builtinRules: () => import('eslint/use-at-your-own-risk').then(r => r.builtinRules) }\` in the options`); const builtinRules = typeof options.builtinRules === "function" ? await options.builtinRules() : options.builtinRules; const rule = builtinRules.get(name); if (!rule) throw new Error(`Rule "${name}" not found in core rules`); disableRuleFixes(rule); } else { const plugins = new Set(configs.map((c) => c.plugins?.[parsed.plugin]).filter((x) => !!x)); for (const plugin of plugins) { hijackPluginRule(plugin, parsed.rule, (rule) => disableRuleFixes(rule)); } } } return configs; }); return this; } setPluginConflictsError(arg1 = DEFAULT_PLUGIN_CONFLICTS_ERROR, arg2) { if (arg2 != null) this._pluginsConflictsError.set(arg1, arg2); else this._pluginsConflictsError.set("*", arg1); return this; } _verifyPluginsConflicts(configs) { if (!this._pluginsConflictsError.size) return; const plugins = /* @__PURE__ */ new Map(); const names = /* @__PURE__ */ new Set(); for (const config of configs) { if (!config.plugins) continue; for (const [name, plugin] of Object.entries(config.plugins)) { names.add(name); if (!plugins.has(plugin)) plugins.set(plugin, { name, configs: [] }); plugins.get(plugin).configs.push(config); } } function getConfigName(config) { return config.name || `#${configs.indexOf(config)}`; } const errors = []; for (const name of names) { const instancesOfName = [...plugins.values()].filter((p) => p.name === name); if (instancesOfName.length <= 1) continue; const configsOfName = instancesOfName.map((p) => p.configs[0]); const message = this._pluginsConflictsError.get(name) || this._pluginsConflictsError.get("*"); if (typeof message === "function") { errors.push(message(name, configsOfName)); } else if (message) { errors.push( message.replace(/\{\{pluginName\}\}/g, name).replace(/\{\{configName1\}\}/g, getConfigName(configsOfName[0])).replace(/\{\{configName2\}\}/g, getConfigName(configsOfName[1])).replace(/\{\{configNames\}\}/g, configsOfName.map(getConfigName).join(", ")) ); } } if (errors.length) { if (errors.length === 1) throw new Error(`ESLintFlatConfigUtils: ${errors[0]}`); else throw new Error(`ESLintFlatConfigUtils: ${errors.map((e, i) => ` ${i + 1}: ${e}`).join("\n")}`); } } /** * Hook when all configs are resolved but before returning the final configs. * * You can modify the final configs here. */ onResolved(callback) { this._operationsResolved.push(callback); return this; } /** * Clone the composer object. */ clone() { const composer2 = new _FlatConfigComposer(); composer2._operations = this._operations.slice(); composer2._operationsOverrides = this._operationsOverrides.slice(); composer2._operationsResolved = this._operationsResolved.slice(); composer2._renames = { ...this._renames }; composer2._pluginsConflictsError = new Map(this._pluginsConflictsError); return composer2; } /** * Resolve the pipeline and return the final configs. * * This returns a promise. Calling `.then()` has the same effect. */ async toConfigs() { let configs = []; for (const promise of this._operations) configs = await promise(configs); for (const promise of this._operationsOverrides) configs = await promise(configs); configs = renamePluginsInConfigs(configs, this._renames); for (const promise of this._operationsResolved) configs = await promise(configs) || configs; this._verifyPluginsConflicts(configs); return configs; } // eslint-disable-next-line ts/explicit-function-return-type then(onFulfilled, onRejected) { return this.toConfigs().then(onFulfilled, onRejected); } // eslint-disable-next-line ts/explicit-function-return-type catch(onRejected) { return this.toConfigs().catch(onRejected); } // eslint-disable-next-line ts/explicit-function-return-type finally(onFinally) { return this.toConfigs().finally(onFinally); } }; function getConfigIndex(configs, nameOrIndex) { if (typeof nameOrIndex === "number") { if (nameOrIndex < 0 || nameOrIndex >= configs.length) throw new Error(`ESLintFlatConfigUtils: Failed to locate config at index ${nameOrIndex} (${configs.length} configs in total)`); return nameOrIndex; } else { const index = configs.findIndex((config) => config.name === nameOrIndex); if (index === -1) { const named = configs.map((config) => config.name).filter(Boolean); const countUnnamed = configs.length - named.length; const messages = [ `Failed to locate config with name "${nameOrIndex}"`, `Available names are: ${named.join(", ")}`, countUnnamed ? `(${countUnnamed} unnamed configs)` : "" ].filter(Boolean).join("\n"); throw new Error(`ESLintFlatConfigUtils: ${messages}`); } return index; } } // src/eslint/globs.ts var GLOB_SRC = "**/*.?([cm])[jt]s?(x)"; var GLOB_TS = "**/*.?([cm])ts"; var GLOB_TSX = "**/*.?([cm])tsx"; var GLOB_JSON = "**/*.json"; var GLOB_JSON5 = "**/*.json5"; var GLOB_JSONC = "**/*.jsonc"; var GLOB_VUE = "**/*.vue"; var GLOB_NODE_MODULES = "**/node_modules"; var GLOB_LOCKFILE = [ "**/package-lock.json", "**/yarn.lock", "**/pnpm-lock.yaml", "**/bun.lockb" ]; var GLOB_EXCLUDE = [ GLOB_NODE_MODULES, ...GLOB_LOCKFILE, // Build folders "**/build", "**/dist", "**/out", "**/target", "public/assets", "**/output", "**/coverage", "**/temp", "**/fixtures", "**/.vitepress/cache", "**/.nuxt", "**/.vercel", "**/.changeset", "**/.idea", "**/.output", "**/.vite-inspect", "**/CHANGELOG*.md", "**/*.min.*", "**/LICENSE*", "**/__snapshots__", "**/auto-import?(s).d.ts", "**/components.d.ts" ]; // src/eslint/utils.ts async function interopDefault(m) { const resolved = await m; return resolved.default || resolved; } // src/eslint/configs/vue.ts async function vue() { const [pluginVue, parserVue] = await Promise.all([ interopDefault(import("eslint-plugin-vue")), interopDefault(import("vue-eslint-parser")) ]); return [ { name: "tpointurier:vue", files: [GLOB_VUE], plugins: { vue: pluginVue }, languageOptions: { parser: parserVue, parserOptions: { ecmaFeatures: { jsx: true }, extraFileExtensions: [".vue"], parser: await interopDefault(import("@typescript-eslint/parser")), sourceType: "module" } }, rules: { ...pluginVue.configs.base.rules, ...pluginVue.configs["vue3-essential"].rules, ...pluginVue.configs["vue3-strongly-recommended"].rules, ...pluginVue.configs["vue3-recommended"].rules, "vue/no-v-html": "off", "vue/require-prop-types": "off", "vue/require-default-prop": "off", "vue/multi-word-component-names": "off", "vue/comment-directive": "off", "vue/block-order": ["error", { order: ["script", "template", "style"] }], "vue/component-name-in-template-casing": ["error", "PascalCase"], "vue/component-options-name-casing": ["error", "PascalCase"], "vue/custom-event-name-casing": ["error", "camelCase"], "vue/define-macros-order": ["error", { order: ["defineProps", "defineEmits"] }], "vue/html-comment-content-spacing": ["error", "always", { exceptions: ["-"] }], "vue/no-restricted-v-bind": ["error", "/^v-/"], "vue/no-useless-v-bind": "error", "vue/no-v-text-v-html-on-component": "error", "vue/padding-line-between-blocks": ["error", "always"], "vue/prefer-separate-static-class": "error", "vue/define-props-declaration": ["error", "type-based"], "vue/dot-notation": ["error", { allowKeywords: true }], "vue/eqeqeq": ["error", "smart"], "vue/no-constant-condition": "warn", "vue/no-empty-pattern": "error", "vue/no-irregular-whitespace": "error", "vue/no-loss-of-precision": "error", "vue/no-restricted-syntax": [ "error", "DebuggerStatement", "LabeledStatement", "WithStatement" ], "vue/no-sparse-arrays": "error", "vue/object-shorthand": [ "error", "always", { ignoreConstructors: false, avoidQuotes: true } ], "vue/prefer-template": "error" } } ]; } // src/eslint/plugins.ts import * as pluginImport from "eslint-plugin-import-x"; import { default as default2 } from "eslint-plugin-n"; import { default as default3 } from "eslint-plugin-unicorn"; // src/eslint/configs/node.ts async function node() { return [ { name: "tpointurier:node", plugins: { node: default2 }, 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/prefer-global/buffer": ["error", "never"], "node/prefer-global/process": ["error", "never"], "node/process-exit-as-throw": "error" } } ]; } // src/eslint/configs/jsonc.ts async function jsonc() { const [pluginJsonc, parserJsonc] = await Promise.all([ interopDefault(import("eslint-plugin-jsonc")), interopDefault(import("jsonc-eslint-parser")) ]); return [ { name: "tpointurier:jsonc", 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" } } ]; } // src/eslint/configs/jsdoc.ts async function jsdoc() { return [ { name: "tpointurier:jsdoc", 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", "jsdoc/check-alignment": "warn", "jsdoc/multiline-blocks": "warn" } } ]; } // src/eslint/configs/unocss.ts async function unocss() { const unoPlugin = await interopDefault(import("@unocss/eslint-plugin")); return [ { name: "tpointurier:unocss", ignores: GLOB_EXCLUDE, plugins: { "@unocss": unoPlugin }, rules: { ...unoPlugin.configs.recommended.rules } } ]; } // src/eslint/configs/ignores.ts async function ignores(userIgnores = []) { return [ { name: "tpointurier:ignores", ignores: [...GLOB_EXCLUDE, ...userIgnores] } ]; } // src/eslint/configs/imports.ts async function imports() { return [ { name: "tpointurier:imports", plugins: { import: pluginImport }, rules: { "import/first": "error", "import/no-mutable-exports": "error", "import/no-duplicates": "error", "import/no-named-default": "error", "import/no-self-import": "error", "import/newline-after-import": ["error", { count: 1 }] } } ]; } // src/eslint/configs/unicorn.ts async function unicorn() { return [ { name: "tpointurier:unicorn", plugins: { unicorn: default3 }, rules: { "unicorn/throw-new-error": "error", "unicorn/no-for-loop": "error", "unicorn/no-await-expression-member": "error", "unicorn/filename-case": ["error", { case: "snakeCase" }], "unicorn/prefer-node-protocol": "error", "unicorn/error-message": "error", "unicorn/escape-case": "error", "unicorn/no-instanceof-array": "error", "unicorn/numeric-separators-style": "error", "unicorn/better-regex": "error", "unicorn/custom-error-definition": "error", "unicorn/no-invalid-remove-event-listener": "error", "unicorn/no-new-array": "error", "unicorn/no-new-buffer": "error", "unicorn/no-useless-spread": "error", "unicorn/number-literal-case": "error", "unicorn/prefer-array-find": "error", "unicorn/prefer-dom-node-text-content": "error", "unicorn/prefer-includes": "error", "unicorn/prefer-number-properties": "error", "unicorn/prefer-string-starts-ends-with": "error", "unicorn/prefer-type-error": "error" } } ]; } // src/eslint/configs/stylistic.ts async function style() { const plugin = await interopDefault(import("@stylistic/eslint-plugin")); return [ { name: "tpointurier:stylistic", plugins: { "@stylistic": plugin }, rules: { "@stylistic/padding-line-between-statements": [ "error", { blankLine: "always", prev: "*", next: ["interface", "type"] } ], "@stylistic/lines-between-class-members": [ "error", "always", { exceptAfterSingleLine: true } ] } } ]; } // src/eslint/configs/prettier.ts async function prettier() { const eslintConfigPrettier = await interopDefault(import("eslint-config-prettier")); const eslintPluginPrettier = await interopDefault(import("eslint-plugin-prettier")); return [ { name: "tpointurier:prettier", plugins: { prettier: eslintPluginPrettier }, rules: { ...eslintConfigPrettier.rules, ...eslintPluginPrettier.configs["recommended"].rules, "prettier/prettier": "warn" } } ]; } // src/eslint/configs/javascript.ts async function javascript() { return [ { name: "tpointurier:javascript", languageOptions: { sourceType: "module", ecmaVersion: 2022, parserOptions: { ecmaFeatures: { jsx: true }, ecmaVersion: 2022, sourceType: "module" } }, linterOptions: { reportUnusedDisableDirectives: true }, rules: { "prefer-const": "error", "no-unused-vars": [ "error", { ignoreRestSiblings: true, argsIgnorePattern: "^_", varsIgnorePattern: "^_" } ], "no-constant-condition": "warn", "no-debugger": "error", "no-cond-assign": ["error", "always"], "no-array-constructor": "error", "no-unreachable": "error", "one-var": ["error", "never"], "eqeqeq": ["error", "always"], "no-caller": "error", "no-control-regex": "error", "no-duplicate-case": "error", "no-eval": "error", "no-ex-assign": "error", "no-extra-boolean-cast": "error", "no-fallthrough": "error", "no-inner-declarations": "error", "no-invalid-regexp": ["error", { allowConstructorFlags: ["u", "y"] }], "no-proto": "error", "no-regex-spaces": "error", "no-self-compare": "error", "no-sparse-arrays": "error", "object-shorthand": ["error", "always", { avoidQuotes: true, ignoreConstructors: false }], "no-unsafe-negation": "error", "no-new-wrappers": "error", "no-self-assign": "error", "no-this-before-super": "error", "no-else-return": "error", "no-with": "error", "no-undef-init": "error", "no-unsafe-finally": "error", "use-isnan": "error", "valid-typeof": ["error", { requireStringLiterals: true }], "curly": ["error", "all"], "yoda": "error", "capitalized-comments": [ "error", "always", { line: { ignorePattern: ".*", ignoreInlineComments: true, ignoreConsecutiveComments: true } } ] } } ]; } // src/eslint/configs/typescript.ts async function typescript(options) { const { parserOptions = {}, enableForVue = false } = options ?? {}; const tsconfigPath = options?.tsconfigPath ? options.tsconfigPath : void 0; const [pluginTs, parserTs] = await Promise.all([ interopDefault(import("@typescript-eslint/eslint-plugin")), interopDefault(import("@typescript-eslint/parser")) ]); const typeAwareRules = { "@typescript-eslint/await-thenable": "error", "@typescript-eslint/dot-notation": ["error", { allowKeywords: true }], "@typescript-eslint/no-floating-promises": "error", "@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-implied-eval": "error", "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/no-unnecessary-type-assertion": "error", "@typescript-eslint/no-unsafe-argument": "error", "@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unsafe-return": "error", "@typescript-eslint/restrict-plus-operands": "error", "@typescript-eslint/restrict-template-expressions": "error", "@typescript-eslint/unbound-method": "error" }; return [ { name: "tpointurier:typescript:setup", plugins: { "@typescript-eslint": pluginTs } }, { name: "tpointurier:typescript", files: [GLOB_SRC, enableForVue ? "**/*.vue" : void 0].filter(Boolean), languageOptions: { parser: parserTs, parserOptions: { extraFileExtensions: enableForVue ? [".vue"] : [], sourceType: "module", ...options?.typeAwareRules ? { projectService: { allowDefaultProject: ["./*.js"], defaultProject: tsconfigPath }, tsconfigRootDir: process.cwd() } : {}, ...options?.forceDecorators ? { emitDecoratorMetadata: true, experimentalDecorators: true } : {}, ...parserOptions } }, rules: { ...pluginTs.configs["eslint-recommended"].overrides[0].rules, ...pluginTs.configs["strict"].rules, "@typescript-eslint/consistent-type-imports": [ "error", { prefer: "type-imports", disallowTypeAnnotations: false } ], "@typescript-eslint/no-use-before-define": [ "error", { functions: false, classes: false, variables: true } ], "@typescript-eslint/ban-ts-comment": [ "error", { "ts-ignore": "allow-with-description", "ts-nocheck": false } ], "@typescript-eslint/prefer-ts-expect-error": "error", "no-useless-constructor": "off", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": [ "error", { args: "all", argsIgnorePattern: "^_", caughtErrors: "all", caughtErrorsIgnorePattern: "^_", destructuredArrayIgnorePattern: "^_", varsIgnorePattern: "^_", ignoreRestSiblings: true } ], "no-redeclare": "off", "@typescript-eslint/no-redeclare": "error", "no-use-before-define": "off", "no-dupe-class-members": "off", "@typescript-eslint/no-dupe-class-members": "error", "no-loss-of-precision": "off", "@typescript-eslint/no-loss-of-precision": "error", "@typescript-eslint/naming-convention": [ "error", { selector: "variable", format: ["camelCase", "UPPER_CASE", "PascalCase"], filter: { regex: "^_", match: false } }, { selector: "typeLike", format: ["PascalCase"] }, { selector: "class", format: ["PascalCase"] }, { selector: "interface", format: ["PascalCase"], custom: { regex: "^I[A-Z]", match: false } } ], // Off "@typescript-eslint/consistent-type-definitions": "off", "@typescript-eslint/consistent-indexed-object-style": "off", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/parameter-properties": "off", "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/ban-ts-ignore": "off", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-namespace": "off", "@typescript-eslint/triple-slash-reference": "off", "@typescript-eslint/no-extraneous-class": "off", // Common pattern in AdonisJS "@typescript-eslint/no-empty-object-type": "off", ...options?.typeAwareRules ? typeAwareRules : {} } }, { files: [GLOB_TS, GLOB_TSX], rules: { "@typescript-eslint/explicit-member-accessibility": [ "error", { accessibility: "no-public" } ] } }, { files: ["**/*.{test,spec}.ts?(x)"], name: "tpointurier:typescript:tests-overrides", rules: { "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/prefer-ts-expect-error": "off" } } ]; } // src/eslint/configs/perfectionist.ts async function perfectionist() { const pluginPerfectionist = await interopDefault(import("eslint-plugin-perfectionist")); return [ { name: "tpointurier:perfectionist", plugins: { perfectionist: pluginPerfectionist }, rules: { "perfectionist/sort-imports": [ "error", { type: "line-length", order: "asc", internalPattern: ["^@/.*", "^#.*/.*", "^~/.*"], groups: [ // Import 'foo.js' or import 'foo.css' ["side-effect", "side-effect-style"], // Packages and node ["builtin", "external", "builtin-type", "external-type"], // Others [ "internal-type", "internal", "parent-type", "sibling-type", "index-type", "parent", "sibling", "index", "style", "object", "unknown" ] ] } ], "perfectionist/sort-enums": ["error", { type: "line-length", order: "asc" }] } } ]; } // src/eslint/configs/sort.ts async function sortPackageJson() { return [ { name: "tpointurier:sort-package-json", files: ["**/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", "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", "eslintConfig" ], pathPattern: "^$" }, { order: { type: "asc" }, pathPattern: "^(?:dev|peer|optional|bundled)?[Dd]ependencies$" }, { order: { type: "asc" }, pathPattern: "^resolutions$" }, { order: { type: "asc" }, pathPattern: "^pnpm.overrides$" }, { order: ["types", "import", "require", "default"], pathPattern: "^exports.*$" } ] } } ]; } async function sortTsconfig() { return [ { name: "tpointurier:sort-tsconfig", 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", "isolatedModules", "preserveSymlinks", "verbatimModuleSyntax", /* Completeness */ "skipDefaultLibCheck", "skipLibCheck" ], pathPattern: "^compilerOptions$" } ] } } ]; } // src/eslint/env.ts import { isPackageExists } from "local-pkg"; var hasTypeScript = isPackageExists("typescript"); var hasVue = isPackageExists("vue") || isPackageExists("nuxt") || isPackageExists("vitepress"); var hasUnocss = isPackageExists("unocss") || isPackageExists("@unocss/webpack") || isPackageExists("@unocss/nuxt"); var hasAdonisjs = isPackageExists("@adonisjs/core"); var hasReact = isPackageExists("react"); // src/eslint/configs/adonisjs.ts async function adonisjs() { const adonisjsPlugin = await interopDefault(import("@adonisjs/eslint-plugin")); return [ { name: "tpointurier:adonisjs", plugins: { "@adonisjs": adonisjsPlugin }, rules: { "@adonisjs/prefer-lazy-controller-import": "error", "@adonisjs/prefer-lazy-listener-import": "error" } } ]; } // src/eslint/configs/react.ts async function react() { const [pluginReact, pluginReactHooks, parserTs] = await Promise.all([ interopDefault(import("eslint-plugin-react")), interopDefault(import("eslint-plugin-react-hooks")), interopDefault(import("@typescript-eslint/parser")) ]); return [ { name: "tpointurier:react", files: [GLOB_TSX], plugins: { react: pluginReact, "react-hooks": pluginReactHooks }, languageOptions: { parser: parserTs, parserOptions: { ecmaFeatures: { jsx: true }, sourceType: "module" } }, rules: { ...pluginReact.configs.recommended.rules, ...pluginReactHooks.configs.recommended.rules, // Désactive ou adapte certaines règles selon tes besoins "react/react-in-jsx-scope": "off", // inutile avec React 17+ "react/prop-types": "off", // si tu utilises TypeScript "react-hooks/exhaustive-deps": "warn", "react/jsx-uses-react": "off", "react/jsx-uses-vars": "off" // Ajoute ici d'autres règles personnalisées si besoin }, settings: { react: { version: "detect" } } } ]; } // src/eslint/index.ts var flatConfigProps = [ "name", "languageOptions", "linterOptions", "processor", "plugins", "rules", "settings" ]; function tpointurier(options = {}, ...userConfigs) { const { enableGitIgnore = true, typescript: enableTypescript = hasTypeScript, vue: enableVue = hasVue, jsonc: enableJsonc = true, prettier: enablePrettier = true, unocss: enableUno = hasUnocss, adonisjs: enableAdonisJs = hasAdonisjs, react: enableReact = false } = options; const configs = []; if (enableGitIgnore) { if (typeof enableGitIgnore !== "boolean") { configs.push( interopDefault(import("eslint-config-flat-gitignore")).then((r) => [ r({ name: "tpointurier:gitignore", ...enableGitIgnore }) ]) ); } else if (existsSync(".gitignore")) { configs.push( interopDefault(import("eslint-config-flat-gitignore")).then((r) => [ r({ name: "tpointurier:gitignore", strict: false }) ]) ); } } configs.push( ignores(options.ignores), javascript(), perfectionist(), imports(), jsdoc(), unicorn(), node(), unicorn(), style() ); if (enableTypescript) { configs.push( typescript({ ...typeof enableTypescript !== "boolean" ? enableTypescript : {}, enableForVue: enableVue }) ); } if (enableVue) { configs.push(vue()); } if (enableUno) { configs.push(unocss()); } if (enableJsonc) { configs.push(jsonc(), sortPackageJson(), sortTsconfig()); } if (enableAdonisJs) { configs.push(adonisjs()); } if (enablePrettier) { configs.push(prettier()); } if (enableReact) { configs.push(react()); } const fusedConfig = flatConfigProps.reduce((acc, key) => { if (key in options) acc[key] = options[key]; return acc; }, {}); if (Object.keys(fusedConfig).length) configs.push([fusedConfig]); const composer = new FlatConfigComposer(); return composer.append(...configs, ...userConfigs); } export { adonisjs, ignores, imports, javascript, jsdoc, jsonc, node, perfectionist, prettier, react, sortPackageJson, sortTsconfig, tpointurier, typescript, unicorn, unocss, vue };