chore(eslint): update shared presets and config exports

This commit is contained in:
2026-06-08 15:50:59 +07:00
parent 5c583b64a4
commit 4678a372b1
10 changed files with 156 additions and 76 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
export { compose } from './compose';
/* Presets */
export { base, ignores, typescript, vue, vitest, imports, node, stylistic } from './presets';
export { base, ignores, typescript, vue, vitest, imports, node, stylistic, regexp } from './presets';
/* Types */
export type {
+45 -23
View File
@@ -2,6 +2,7 @@ import type { FlatConfigArray } from '../types';
import js from '@eslint/js';
import unicorn from 'eslint-plugin-unicorn';
import globals from 'globals';
import { regexp } from './regexp';
/**
* Globally ignored paths — build output, coverage, generated artifacts.
@@ -61,41 +62,62 @@ export const base: FlatConfigArray = [
'no-eval': 'error',
'no-var': 'error',
'prefer-const': 'error',
'prefer-template': 'warn',
'no-useless-constructor': 'warn',
'no-useless-rename': 'warn',
'prefer-template': 'error',
'no-useless-constructor': 'error',
'no-useless-rename': 'error',
'no-unused-vars': ['error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }],
'no-self-compare': 'error',
'no-template-curly-in-string': 'warn',
'no-template-curly-in-string': 'error',
'no-throw-literal': 'error',
'no-return-assign': 'warn',
'no-else-return': 'warn',
'no-lonely-if': 'warn',
'no-unneeded-ternary': 'warn',
'prefer-object-spread': 'warn',
'prefer-exponentiation-operator': 'warn',
'no-useless-computed-key': 'warn',
'no-useless-concat': 'warn',
'no-return-assign': 'error',
'no-else-return': 'error',
'no-lonely-if': 'error',
'no-unneeded-ternary': 'error',
'prefer-object-spread': 'error',
'prefer-exponentiation-operator': 'error',
'no-useless-computed-key': 'error',
'no-useless-concat': 'error',
'no-array-constructor': 'error',
'no-new-wrappers': 'error',
'no-useless-return': 'error',
'object-shorthand': ['error', 'always'],
'prefer-spread': 'error',
'prefer-rest-params': 'error',
'symbol-description': 'error',
curly: 'off',
/* ── unicorn ──────────────────────────────────────────── */
'unicorn/prefer-node-protocol': 'error',
'unicorn/no-instanceof-builtins': 'error',
'unicorn/no-new-array': 'error',
'unicorn/prefer-array-flat-map': 'warn',
'unicorn/prefer-array-flat': 'warn',
'unicorn/prefer-includes': 'warn',
'unicorn/prefer-string-slice': 'warn',
'unicorn/prefer-string-starts-ends-with': 'warn',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-flat': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-string-slice': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/throw-new-error': 'error',
'unicorn/error-message': 'warn',
'unicorn/no-useless-spread': 'warn',
'unicorn/error-message': 'error',
'unicorn/no-useless-spread': 'error',
'unicorn/no-useless-undefined': 'off',
'unicorn/prefer-optional-catch-binding': 'warn',
'unicorn/prefer-type-error': 'warn',
'unicorn/prefer-optional-catch-binding': 'error',
'unicorn/prefer-type-error': 'error',
'unicorn/no-thenable': 'error',
'unicorn/prefer-number-properties': 'warn',
'unicorn/prefer-global-this': 'warn',
'unicorn/prefer-number-properties': 'error',
'unicorn/prefer-global-this': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-index-of': 'error',
'unicorn/prefer-date-now': 'error',
'unicorn/prefer-modern-math-apis': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-set-has': 'error',
'unicorn/prefer-string-trim-start-end': 'error',
'unicorn/prefer-regexp-test': 'error',
'unicorn/prefer-string-replace-all': 'error',
'unicorn/no-typeof-undefined': 'error',
'unicorn/no-array-push-push': 'error',
'unicorn/no-useless-promise-resolve-reject': 'error',
},
},
...regexp,
];
+8 -6
View File
@@ -25,17 +25,19 @@ export const imports: FlatConfigArray = [
rules: {
'import-x/no-duplicates': 'error',
'import-x/no-self-import': 'error',
'import-x/no-cycle': 'warn',
'import-x/first': 'warn',
'import-x/no-cycle': 'error',
'import-x/first': 'error',
'import-x/no-mutable-exports': 'error',
'import-x/no-amd': 'error',
'import-x/no-commonjs': 'warn',
'import-x/no-empty-named-blocks': 'warn',
'import-x/consistent-type-specifier-style': ['warn', 'prefer-top-level'],
'import-x/no-commonjs': 'error',
'import-x/no-empty-named-blocks': 'error',
'import-x/no-useless-path-segments': ['error', { noUselessIndex: false }],
'import-x/consistent-type-specifier-style': ['error', 'prefer-top-level'],
/* Only enforce member order within `{ … }`; declaration order is sorted
by source path across the codebase, which core `sort-imports` (orders
by first member name) would otherwise fight. */
by first member name) would otherwise fight. Kept at `warn` — it is not
autofixable and member order is a soft preference. */
'sort-imports': ['warn', { ignoreDeclarationSort: true }],
},
},
+1
View File
@@ -4,4 +4,5 @@ export { vue } from './vue';
export { vitest } from './vitest';
export { imports } from './imports';
export { node } from './node';
export { regexp } from './regexp';
export { stylistic } from './stylistic';
+18
View File
@@ -0,0 +1,18 @@
import type { FlatConfigArray } from '../types';
import regexpPlugin from 'eslint-plugin-regexp';
/**
* Regular-expression correctness & optimization rules via
* [`eslint-plugin-regexp`](https://ota-meshi.github.io/eslint-plugin-regexp/).
*
* Applies the plugin's flat `recommended` ruleset — catches buggy/ambiguous
* patterns (control characters, useless quantifiers, ReDoS-prone constructs)
* and pushes toward clearer, faster expressions. Included in {@link base} so it
* applies to every package.
*/
export const regexp: FlatConfigArray = [
{
...regexpPlugin.configs['flat/recommended'],
name: 'robonen/regexp',
},
];
+1 -1
View File
@@ -12,7 +12,7 @@ import stylisticPlugin from '@stylistic/eslint-plugin';
* - commaDangle: always-multiline
* - arrowParens: as-needed
* - blockSpacing: true
* - quoteProps: consistent-as-needed
* - quoteProps: as-needed
* - jsx: true
*
* @see https://eslint.style/guide/config-presets
+36 -23
View File
@@ -4,15 +4,22 @@ import tseslint from 'typescript-eslint';
/**
* TypeScript-specific configuration.
*
* Pulls in `typescript-eslint`'s recommended (non type-checked) setup — which
* registers the parser/plugin and disables core rules superseded by their
* TypeScript-aware counterparts — then layers opinionated rules on top.
* Adopts `typescript-eslint`'s **strict** (non type-checked) ruleset plus the
* **stylistic** ruleset — registering the parser/plugin, disabling core rules
* superseded by TS-aware counterparts, and enforcing the full strict + stylistic
* sets at `error`. A small overlay re-tunes a few rules for this monorepo.
*
* Two deliberate carve-outs: `no-explicit-any` is kept at `warn` (the low-level
* stdlib/toolkit does a lot of type-boundary work where `any` is idiomatic), and
* `no-non-null-assertion` is `off` (the `!` operator is how the codebase satisfies
* `noUncheckedIndexedAccess` on provably-bounded indexed access).
*
* `.vue` files are included so the rules apply inside `<script lang="ts">`
* blocks; the `vue` preset assigns the matching parser for them.
*/
export const typescript: FlatConfigArray = [
...tseslint.configs.recommended,
...tseslint.configs.strict,
...tseslint.configs.stylistic,
{
name: 'robonen/typescript',
files: ['**/*.ts', '**/*.tsx', '**/*.mts', '**/*.cts', '**/*.vue'],
@@ -25,27 +32,33 @@ export const typescript: FlatConfigArray = [
false positives (e.g. globals, auto-imports, compiler macros). */
'no-undef': 'off',
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/no-explicit-any': 'off',
/* Deliberate carve-outs from `strict` (see file header). */
'@typescript-eslint/no-explicit-any': 'warn',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/prefer-as-const': 'error',
'@typescript-eslint/no-empty-object-type': ['warn', { allowInterfaces: 'with-single-extends' }],
'@typescript-eslint/no-wrapper-object-types': 'error',
'@typescript-eslint/no-duplicate-enum-values': 'error',
'@typescript-eslint/no-unsafe-declaration-merging': 'error',
/* noop/default callbacks (`() => {}`) are an intentional, pervasive pattern. */
'@typescript-eslint/no-empty-function': 'off',
/* The libraries expose deliberate overload signatures (better inference/DX
than a single union signature) — don't force-merge them. */
'@typescript-eslint/unified-signatures': 'off',
/* Plain objects are used as keyed dictionaries (e.g. forms errors/touched
maps) where dynamic `delete` is legitimate. */
'@typescript-eslint/no-dynamic-delete': 'off',
/* Index-based `for` loops are sometimes a deliberate perf choice; this rule
is not autofixable and converting can subtly change semantics. */
'@typescript-eslint/prefer-for-of': 'off',
/* Idiomatic callback return unions (`() => void | false`, `() => void |
Promise<T>`) are pervasive in composables; the rule is hostile to them. */
'@typescript-eslint/no-invalid-void-type': 'off',
/* Allow our type-helper interfaces that extend a single mapped type. */
'@typescript-eslint/no-empty-object-type': ['error', { allowInterfaces: 'with-single-extends' }],
/* House preferences (override the strict/stylistic defaults' options). */
'@typescript-eslint/consistent-type-imports': 'error',
'@typescript-eslint/consistent-type-definitions': ['error', 'interface'],
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
'@typescript-eslint/no-import-type-side-effects': 'error',
'@typescript-eslint/no-useless-empty-export': 'warn',
'@typescript-eslint/no-inferrable-types': 'warn',
'@typescript-eslint/prefer-function-type': 'warn',
'@typescript-eslint/ban-tslint-comment': 'error',
'@typescript-eslint/consistent-type-definitions': ['warn', 'interface'],
'@typescript-eslint/prefer-for-of': 'warn',
'@typescript-eslint/no-unnecessary-type-constraint': 'warn',
'@typescript-eslint/adjacent-overload-signatures': 'warn',
'@typescript-eslint/array-type': ['warn', { default: 'array-simple' }],
'@typescript-eslint/no-this-alias': 'error',
'@typescript-eslint/triple-slash-reference': 'error',
'@typescript-eslint/no-namespace': 'error',
'@typescript-eslint/no-useless-empty-export': 'error',
},
},
];
+20 -9
View File
@@ -4,8 +4,9 @@ import vitestPlugin from '@vitest/eslint-plugin';
/**
* Vitest configuration for test files.
*
* Scoped to common test file patterns; also relaxes a few strict rules that
* are noisy in tests.
* Scoped to common test file patterns. Adopts the plugin's full `recommended`
* ruleset, layers extra preference rules at `error`, and relaxes a few strict
* rules that are noisy in tests.
*/
export const vitest: FlatConfigArray = [
{
@@ -20,19 +21,29 @@ export const vitest: FlatConfigArray = [
vitest: vitestPlugin,
},
rules: {
'vitest/no-conditional-tests': 'warn',
...vitestPlugin.configs.recommended.rules,
/* House convention: `describe(useX, …)` / `it(fn, …)` pass a FUNCTION as the
title (nicer reporter output) — valid-title only accepts strings. */
'vitest/valid-title': 'off',
/* Niche stylistic preference; the explicit two-assertion form is clearer. */
'vitest/prefer-called-exactly-once-with': 'off',
'vitest/no-import-node-test': 'error',
'vitest/prefer-to-be-truthy': 'warn',
'vitest/prefer-to-be-falsy': 'warn',
'vitest/prefer-to-be-object': 'warn',
'vitest/prefer-to-have-length': 'warn',
'vitest/consistent-test-filename': 'warn',
'vitest/prefer-describe-function-title': 'warn',
'vitest/no-conditional-tests': 'error',
'vitest/prefer-to-be-truthy': 'error',
'vitest/prefer-to-be-falsy': 'error',
'vitest/prefer-to-be-object': 'error',
'vitest/prefer-to-have-length': 'error',
'vitest/consistent-test-filename': 'error',
'vitest/prefer-describe-function-title': 'error',
/* relax strict rules in tests */
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-explicit-any': 'off',
/* Empty mock/fixture classes (e.g. stubbing `class DeviceOrientationEvent {}`). */
'@typescript-eslint/no-extraneous-class': 'off',
},
},
];
+25 -13
View File
@@ -1,16 +1,28 @@
import type { FlatConfigArray } from '../types';
import type { FlatConfigArray, Rules } from '../types';
import pluginVue from 'eslint-plugin-vue';
import tseslint from 'typescript-eslint';
import vueParser from 'vue-eslint-parser';
/**
* Merge the rule maps from every config object in an eslint-plugin-vue flat
* preset (they ship as arrays) into a single rules record.
*/
function collectRules(configs: Array<{ rules?: unknown }>): Rules {
return configs.reduce<Rules>((rules, config) => ({ ...rules, ...(config.rules as Rules | undefined) }), {});
}
/**
* The Priority-A "Essential" (error-prevention) ruleset from eslint-plugin-vue.
*/
const essentialRules = collectRules(pluginVue.configs['flat/essential'] as Array<{ rules?: unknown }>);
/**
* Vue.js configuration.
*
* Registers `eslint-plugin-vue` with `vue-eslint-parser` (delegating
* `<script lang="ts">` to the TypeScript parser) and enables an opinionated
* subset that enforces Composition API with `<script setup>` and type-based
* declarations. Only the listed rules are turned on — the plugin's large
* `recommended` set is intentionally not pulled in.
* `<script lang="ts">` to the TypeScript parser), adopts the plugin's full
* **Essential** (Priority-A) ruleset, and layers opinionated rules that enforce
* the Composition API with `<script setup>` and type-based declarations.
*/
export const vue: FlatConfigArray = [
{
@@ -34,19 +46,19 @@ export const vue: FlatConfigArray = [
name: 'robonen/vue/rules',
files: ['**/*.vue'],
rules: {
'vue/no-arrow-functions-in-watch': 'error',
'vue/no-deprecated-destroyed-lifecycle': 'error',
'vue/no-export-in-script-setup': 'error',
'vue/no-lifecycle-after-await': 'error',
...essentialRules,
/* Component library: single-word component names (Primitive, Slot, …) are intentional. */
'vue/multi-word-component-names': 'off',
/* House additions / stricter opinions on top of Essential. */
'vue/no-multiple-slot-args': 'error',
'vue/no-import-compiler-macros': 'error',
'vue/define-emits-declaration': ['error', 'type-based'],
'vue/define-props-declaration': ['error', 'type-based'],
'vue/prefer-import-from-vue': 'error',
'vue/no-required-prop-with-default': 'warn',
'vue/valid-define-emits': 'error',
'vue/valid-define-props': 'error',
'vue/require-typed-ref': 'warn',
'vue/no-required-prop-with-default': 'error',
'vue/require-typed-ref': 'error',
},
},
];