@tpointurier/configs
Version:
ESLint config presets
1,444 lines (1,421 loc) • 43.9 kB
JavaScript
// 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
};