@heyq02/rslint-config 0.0.2 → 0.0.3-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,12 @@
1
1
  # @heyq02/rslint-config
2
2
 
3
- [![npm version][npm-version-src]][npm-version-href]
4
- [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
- [![Unit Test][unit-test-src]][unit-test-href]
3
+ <!-- automd:badges license -->
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@heyq02/rslint-config)](https://npmjs.com/package/@heyq02/rslint-config)
6
+ [![npm downloads](https://img.shields.io/npm/dm/@heyq02/rslint-config)](https://npm.chart.dev/@heyq02/rslint-config)
7
+ [![license](https://img.shields.io/github/license/heyq02/rslint-config)](https://github.com/heyq02/rslint-config/blob/main/LICENSE)
8
+
9
+ <!-- /automd -->
6
10
 
7
11
  Opinionated [Rslint](https://rslint.rs/) flat-config preset, structured like [`@sxzz/eslint-config`](https://github.com/sxzz/eslint-config) and built on `@rslint/core` native presets.
8
12
 
@@ -11,6 +15,8 @@ Opinionated [Rslint](https://rslint.rs/) flat-config preset, structured like [`@
11
15
  - One-line config via `heyq02()`
12
16
  - JavaScript + TypeScript presets (`recommended` / `strict` / type-checked / stylistic)
13
17
  - Built-in Unicorn, import, Node, and Promise rules
18
+ - Import sorting via `import/order` with an [Oxfmt `sortImports`](https://oxc.rs/docs/guide/usage/formatter/sorting.html)-shaped API
19
+ - Community plugins (Rslint-compatible): unused-imports, regexp, de-morgan, eslint-comments
14
20
  - Optional React, React Hooks, JSX a11y, Jest, and Rstest (auto-detected)
15
21
  - Ignores common build artifacts (`dist`, `coverage`, lockfiles, …)
16
22
  - Special-case overrides for CLIs, tests, config files, and components
@@ -52,28 +58,72 @@ export default heyq02(
52
58
 
53
59
  ### Options
54
60
 
55
- | Option | Default | Description |
56
- | ------------ | ------------------- | ----------------------------------------------------------------------- |
57
- | `typescript` | auto (`typescript`) | `true` / `false` / `{ typeChecked, strict, stylistic, projectService }` |
58
- | `javascript` | `true` | JavaScript recommended + opinionated rules |
59
- | `unicorn` | `true` | Unicorn recommended + overrides |
60
- | `imports` | `true` | Import recommended + no-default-export, etc. |
61
- | `node` | `true` (`module`) | `true` / `'module'` / `'script'` / `'mixed'` |
62
- | `promise` | `true` | Promise recommended (relaxed for async/await) |
63
- | `react` | auto (`react`) | React + React Hooks (+ jsx-a11y) |
64
- | `jsxA11y` | with react | JSX accessibility rules |
65
- | `jest` | auto (`jest`) | Jest recommended + style |
66
- | `rstest` | auto (`rstest`) | Rstest recommended |
67
- | `ignores` | `[]` | Extra global ignore patterns |
61
+ | Option | Default | Description |
62
+ | ---------------- | ------------------- | --------------------------------------------------------------------------------------------- |
63
+ | `typescript` | auto (`typescript`) | `true` / `false` / `{ typeChecked, strict, stylistic, projectService }` |
64
+ | `javascript` | `true` | JavaScript recommended + opinionated rules |
65
+ | `unicorn` | `true` | Unicorn recommended + overrides |
66
+ | `imports` | `true` | Import recommended + no-default-export, etc. |
67
+ | `sortImports` | `true` | `true` / `false` / Oxfmt-like `{ groups, newlinesBetween, customGroups, … }` → `import/order` |
68
+ | `node` | `true` (`module`) | `true` / `'module'` / `'script'` / `'mixed'` |
69
+ | `promise` | `true` | Promise recommended (relaxed for async/await) |
70
+ | `react` | auto (`react`) | React + React Hooks (+ jsx-a11y) |
71
+ | `jsxA11y` | with react | JSX accessibility rules |
72
+ | `jest` | auto (`jest`) | Jest recommended + style |
73
+ | `rstest` | auto (`rstest`) | Rstest recommended |
74
+ | `unusedImports` | `true` | `eslint-plugin-unused-imports` (autofix unused imports) |
75
+ | `regexp` | `true` | `eslint-plugin-regexp` |
76
+ | `deMorgan` | `true` | `eslint-plugin-de-morgan` |
77
+ | `eslintComments` | `true` | `@eslint-community/eslint-plugin-eslint-comments` |
78
+ | `ignores` | `[]` | Extra global ignore patterns |
79
+
80
+ ### Sort imports
81
+
82
+ Enabled by default (when `imports` is on). Options mirror Oxfmt [`sortImports`](https://oxc.rs/docs/guide/usage/formatter/sorting.html) where Rslint can enforce them via [`import/order`](https://rslint.rs/rules/import/order):
83
+
84
+ ```ts
85
+ export default heyq02({
86
+ sortImports: {
87
+ groups: [
88
+ 'type',
89
+ ['builtin', 'external'],
90
+ 'internal',
91
+ ['parent', 'sibling', 'index'],
92
+ 'unknown',
93
+ ],
94
+ newlinesBetween: true,
95
+ internalPattern: ['~/', '@/', '#'],
96
+ customGroups: [
97
+ {
98
+ groupName: 'react-libs',
99
+ elementNamePattern: ['react', 'react-**'],
100
+ group: 'external',
101
+ position: 'before',
102
+ },
103
+ ],
104
+ order: 'asc',
105
+ ignoreCase: true,
106
+ named: true,
107
+ },
108
+ });
109
+ ```
110
+
111
+ | Oxfmt option | This package | Notes |
112
+ | ------------------------------------------- | ----------------- | --------------------------------------------------------------------------------------------------- |
113
+ | `groups` | `groups` | Uses `import/order` names (`builtin`, `type`, …), not perfectionist selectors (`value-builtin`) |
114
+ | `newlinesBetween` | `newlinesBetween` | `boolean` → `always` / `never`; per-group `{ newlinesBetween }` markers unsupported |
115
+ | `customGroups` | `customGroups` | Mapped to `pathGroups` (`group` + `position` required conceptually; defaults `external` / `before`) |
116
+ | `internalPattern` | `internalPattern` | Glob-like prefixes → `import/internal-regex` |
117
+ | `order` / `ignoreCase` | same | → `alphabetize` |
118
+ | `sortSideEffects` | `sortSideEffects` | → `warnOnUnassignedImports` |
119
+ | `partitionByComment` / `partitionByNewline` | — | Not supported by `import/order` |
120
+
121
+ If Oxfmt already sorts imports, set `sortImports: false` to avoid fighting the formatter.
68
122
 
69
123
  ### Presets
70
124
 
71
125
  ```ts
72
- import {
73
- presetJavaScript,
74
- presetBasic,
75
- presetAll,
76
- } from '@heyq02/rslint-config';
126
+ import { presetJavaScript, presetBasic, presetAll } from '@heyq02/rslint-config';
77
127
 
78
128
  export default presetBasic();
79
129
  ```
@@ -85,18 +135,28 @@ pnpm install
85
135
  pnpm run build
86
136
  pnpm run lint
87
137
  pnpm run typecheck
88
- pnpm release # bumpp → tag v* → GitHub Release + npm publish
138
+ pnpm run format # automd + vp fmt (Oxfmt via Vite+)
139
+ pnpm run check # format:check + rslint + tsc
140
+ pnpm release # bumpp → tag v* → GitHub Release + npm publish
89
141
  ```
90
142
 
91
143
  ## License
92
144
 
93
- [MIT](./LICENSE) © Ari
145
+ <!-- automd:contributors license=MIT author="heyq02" -->
146
+
147
+ Published under the [MIT](https://github.com/heyq02/rslint-config/blob/main/LICENSE) license.
148
+ Made by [@heyq02](https://github.com/heyq02) and [community](https://github.com/heyq02/rslint-config/graphs/contributors) 💛
149
+ <br><br>
150
+ <a href="https://github.com/heyq02/rslint-config/graphs/contributors">
151
+ <img src="https://contrib.rocks/image?repo=heyq02/rslint-config" />
152
+ </a>
153
+
154
+ <!-- /automd -->
155
+
156
+ <!-- automd:with-automd -->
157
+
158
+ ---
94
159
 
95
- <!-- Badges -->
160
+ _🤖 auto updated with [automd](https://automd.unjs.io)_
96
161
 
97
- [npm-version-src]: https://img.shields.io/npm/v/@heyq02/rslint-config.svg
98
- [npm-version-href]: https://npmjs.com/package/@heyq02/rslint-config
99
- [npm-downloads-src]: https://img.shields.io/npm/dm/@heyq02/rslint-config
100
- [npm-downloads-href]: https://www.npmcharts.com/compare/@heyq02/rslint-config?interval=30
101
- [unit-test-src]: https://github.com/heyq02/rslint-config/actions/workflows/unit-test.yml/badge.svg
102
- [unit-test-href]: https://github.com/heyq02/rslint-config/actions/workflows/unit-test.yml
162
+ <!-- /automd -->
@@ -0,0 +1,5 @@
1
+ import type { TypedFlatConfig } from '../types.js';
2
+ /**
3
+ * De Morgan rewrites via `eslint-plugin-de-morgan` (Rslint community plugin).
4
+ */
5
+ export declare const deMorgan: () => TypedFlatConfig[];
@@ -0,0 +1,5 @@
1
+ import type { TypedFlatConfig } from '../types.js';
2
+ /**
3
+ * `eslint-disable` hygiene via `@eslint-community/eslint-plugin-eslint-comments`.
4
+ */
5
+ export declare const eslintComments: () => TypedFlatConfig[];
@@ -1,4 +1,10 @@
1
- import type { TypedFlatConfig } from '../types.js';
2
- export declare const imports: (options?: {
1
+ import type { SortImportsOptions, TypedFlatConfig } from '../types.js';
2
+ export interface ImportsOptions {
3
3
  typescript?: boolean;
4
- }) => TypedFlatConfig[];
4
+ /**
5
+ * Enable Oxfmt-shaped import sorting via `import/order`.
6
+ * @default true
7
+ */
8
+ sortImports?: boolean | SortImportsOptions;
9
+ }
10
+ export declare const imports: (options?: ImportsOptions) => TypedFlatConfig[];
@@ -1,3 +1,5 @@
1
+ export * from './de-morgan.js';
2
+ export * from './eslint-comments.js';
1
3
  export * from './ignores.js';
2
4
  export * from './imports.js';
3
5
  export * from './javascript.js';
@@ -5,7 +7,10 @@ export * from './jest.js';
5
7
  export * from './node.js';
6
8
  export * from './promise.js';
7
9
  export * from './react.js';
10
+ export * from './regexp.js';
8
11
  export * from './rstest.js';
12
+ export * from './sort-imports.js';
9
13
  export * from './special-cases.js';
10
14
  export * from './typescript.js';
11
15
  export * from './unicorn.js';
16
+ export * from './unused-imports.js';
@@ -0,0 +1,5 @@
1
+ import type { TypedFlatConfig } from '../types.js';
2
+ /**
3
+ * RegExp quality rules via `eslint-plugin-regexp` (Rslint community plugin).
4
+ */
5
+ export declare const regexp: () => TypedFlatConfig[];
@@ -0,0 +1,42 @@
1
+ import type { ImportOrderOptions } from '@rslint/core';
2
+ import type { SortImportsCustomGroup, SortImportsOptions } from '../types.js';
3
+ /** eslint-plugin-import / Rslint `import/order` group names. */
4
+ export type ImportOrderGroup = 'builtin' | 'external' | 'internal' | 'unknown' | 'parent' | 'sibling' | 'index' | 'object' | 'type';
5
+ type ImportOrderConfig = NonNullable<ImportOrderOptions[0]>;
6
+ /**
7
+ * Default group order inspired by Oxfmt / perfectionist `sort-imports`
8
+ * (type-first, then builtin+external, internal, relative, unknown).
9
+ *
10
+ * Mapped onto `import/order` groups — modifier-selectors like
11
+ * `value-builtin` / `type-internal` are not expressible 1:1, so types are
12
+ * collected via the dedicated `type` group.
13
+ *
14
+ * @see https://oxc.rs/docs/guide/usage/formatter/sorting.html
15
+ */
16
+ export declare const DEFAULT_SORT_IMPORT_GROUPS: (ImportOrderGroup | ImportOrderGroup[])[];
17
+ /** Oxfmt default prefixes for internal modules. */
18
+ export declare const DEFAULT_INTERNAL_PATTERN: string[];
19
+ /**
20
+ * Convert Oxfmt-style glob prefixes (`~/`, `@/`, `#`) into a single
21
+ * `import/internal-regex` source string.
22
+ */
23
+ export declare function internalPatternToRegex(patterns: string[]): string;
24
+ /**
25
+ * Map Oxfmt-like `customGroups` onto `import/order` `pathGroups`.
26
+ *
27
+ * Custom group names are not first-class in `import/order`; each entry must
28
+ * attach to a real group via `group` / `position` (defaults: `external` /
29
+ * `before`), matching the common “react before other externals” pattern.
30
+ */
31
+ export declare function customGroupsToPathGroups(customGroups: SortImportsCustomGroup[]): Exclude<ImportOrderConfig['pathGroups'], undefined>;
32
+ /**
33
+ * Translate Oxfmt-shaped `sortImports` options into `import/order` options +
34
+ * settings. Options that cannot be honored (per-group `newlinesBetween`
35
+ * markers, `partitionByComment`, perfectionist modifier-selectors, etc.) are
36
+ * intentionally omitted — see README.
37
+ */
38
+ export declare function resolveSortImports(options?: true | SortImportsOptions): {
39
+ rule: ImportOrderConfig;
40
+ settings: Record<string, unknown>;
41
+ };
42
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { TypedFlatConfig } from '../types.js';
2
+ /**
3
+ * Autofix unused imports via `eslint-plugin-unused-imports` (Rslint community plugin).
4
+ */
5
+ export declare const unusedImports: () => TypedFlatConfig[];
package/dist/index.js CHANGED
@@ -1,5 +1,9 @@
1
+ import eslint_plugin_de_morgan from "eslint-plugin-de-morgan";
2
+ import eslint_plugin_eslint_comments from "@eslint-community/eslint-plugin-eslint-comments";
1
3
  import { globalIgnores, globals, importPlugin, jestPlugin, js, jsxA11yPlugin, nodePlugin, promisePlugin, reactHooksPlugin, reactPlugin, rstestPlugin, ts, unicornPlugin } from "@rslint/core";
2
- import { createRequire } from "node:module";
4
+ import eslint_plugin_regexp from "eslint-plugin-regexp";
5
+ import eslint_plugin_unused_imports from "eslint-plugin-unused-imports";
6
+ import { isPackageExists } from "local-pkg";
3
7
  import node_process from "node:process";
4
8
  const GLOB_SRC_EXT = '?([cm])[jt]s?(x)';
5
9
  const GLOB_SRC = `**/*.${GLOB_SRC_EXT}`;
@@ -49,6 +53,47 @@ const GLOB_EXCLUDE = [
49
53
  '**/auto-import?(s).d.ts',
50
54
  '**/components.d.ts'
51
55
  ];
56
+ function asESLintPlugin(plugin) {
57
+ return plugin;
58
+ }
59
+ const deMorgan = ()=>[
60
+ {
61
+ files: [
62
+ GLOB_SRC
63
+ ],
64
+ name: 'heyq02/de-morgan',
65
+ plugins: {
66
+ 'de-morgan': asESLintPlugin(eslint_plugin_de_morgan)
67
+ },
68
+ rules: {
69
+ 'de-morgan/no-negated-conjunction': 'error',
70
+ 'de-morgan/no-negated-disjunction': 'error'
71
+ }
72
+ }
73
+ ];
74
+ const eslintComments = ()=>[
75
+ {
76
+ files: [
77
+ GLOB_SRC
78
+ ],
79
+ name: 'heyq02/eslint-comments',
80
+ plugins: {
81
+ '@eslint-community/eslint-comments': asESLintPlugin(eslint_plugin_eslint_comments)
82
+ },
83
+ rules: {
84
+ '@eslint-community/eslint-comments/disable-enable-pair': [
85
+ 'error',
86
+ {
87
+ allowWholeFile: true
88
+ }
89
+ ],
90
+ '@eslint-community/eslint-comments/no-aggregating-enable': 'error',
91
+ '@eslint-community/eslint-comments/no-duplicate-disable': 'error',
92
+ '@eslint-community/eslint-comments/no-unlimited-disable': 'error',
93
+ '@eslint-community/eslint-comments/no-unused-enable': 'error'
94
+ }
95
+ }
96
+ ];
52
97
  const ignores = (extra = [])=>[
53
98
  {
54
99
  ...globalIgnores([
@@ -58,7 +103,96 @@ const ignores = (extra = [])=>[
58
103
  name: 'heyq02/global-ignores'
59
104
  }
60
105
  ];
61
- const imports = (options = {})=>[
106
+ const DEFAULT_SORT_IMPORT_GROUPS = [
107
+ 'type',
108
+ [
109
+ 'builtin',
110
+ 'external'
111
+ ],
112
+ 'internal',
113
+ [
114
+ 'parent',
115
+ 'sibling',
116
+ 'index'
117
+ ],
118
+ 'unknown'
119
+ ];
120
+ const DEFAULT_INTERNAL_PATTERN = [
121
+ '~/',
122
+ '@/',
123
+ '#'
124
+ ];
125
+ const PREDEFINED_GROUPS = new Set([
126
+ 'builtin',
127
+ 'external',
128
+ 'internal',
129
+ 'unknown',
130
+ 'parent',
131
+ 'sibling',
132
+ 'index',
133
+ 'object',
134
+ 'type'
135
+ ]);
136
+ function internalPatternToRegex(patterns) {
137
+ const escaped = patterns.map((pattern)=>pattern.replaceAll(/[.*+?^${}()|[\]\\]/g, '\\$&')).filter(Boolean);
138
+ if (0 === escaped.length) return '';
139
+ return `^(?:${escaped.join('|')})`;
140
+ }
141
+ function toPatterns(value) {
142
+ return Array.isArray(value) ? value : [
143
+ value
144
+ ];
145
+ }
146
+ function customGroupsToPathGroups(customGroups) {
147
+ return customGroups.flatMap((entry)=>{
148
+ const group = entry.group ?? 'external';
149
+ const position = entry.position ?? 'before';
150
+ return toPatterns(entry.elementNamePattern).map((pattern)=>({
151
+ pattern,
152
+ group,
153
+ position
154
+ }));
155
+ });
156
+ }
157
+ function resolveSortImports(options = true) {
158
+ const opts = true === options ? {} : options;
159
+ const { groups = DEFAULT_SORT_IMPORT_GROUPS, newlinesBetween = true, customGroups = [], internalPattern = DEFAULT_INTERNAL_PATTERN, order = 'asc', ignoreCase = true, named = true, sortSideEffects = false } = opts;
160
+ const pathGroups = customGroupsToPathGroups(customGroups);
161
+ const normalizedGroups = groups.map((item)=>{
162
+ if (Array.isArray(item)) {
163
+ const filtered = item.filter((g)=>PREDEFINED_GROUPS.has(g));
164
+ return filtered.length > 0 ? filtered : null;
165
+ }
166
+ return PREDEFINED_GROUPS.has(item) ? item : null;
167
+ }).filter((item)=>Boolean(item));
168
+ const rule = {
169
+ groups: normalizedGroups.length > 0 ? normalizedGroups : DEFAULT_SORT_IMPORT_GROUPS,
170
+ 'newlines-between': newlinesBetween ? 'always' : 'never',
171
+ alphabetize: {
172
+ order,
173
+ caseInsensitive: ignoreCase
174
+ },
175
+ named,
176
+ warnOnUnassignedImports: sortSideEffects,
177
+ ...pathGroups.length > 0 ? {
178
+ pathGroups,
179
+ pathGroupsExcludedImportTypes: [
180
+ 'builtin',
181
+ 'object'
182
+ ]
183
+ } : {}
184
+ };
185
+ const settings = {};
186
+ const internalRegex = internalPatternToRegex(internalPattern);
187
+ if (internalRegex) settings['import/internal-regex'] = internalRegex;
188
+ return {
189
+ rule,
190
+ settings
191
+ };
192
+ }
193
+ const imports = (options = {})=>{
194
+ const { typescript = false, sortImports: sortImportsOption = true } = options;
195
+ const configs = [
62
196
  {
63
197
  ...importPlugin.configs.recommended,
64
198
  files: [
@@ -82,7 +216,7 @@ const imports = (options = {})=>[
82
216
  ],
83
217
  'import/no-mutable-exports': 'error',
84
218
  'import/no-named-default': 'error',
85
- ...options.typescript ? {
219
+ ...typescript ? {
86
220
  'import/default': 'off',
87
221
  'import/named': 'off',
88
222
  'import/namespace': 'off',
@@ -91,6 +225,26 @@ const imports = (options = {})=>[
91
225
  }
92
226
  }
93
227
  ];
228
+ if (sortImportsOption) {
229
+ const { rule, settings } = resolveSortImports(sortImportsOption);
230
+ configs.push({
231
+ files: [
232
+ GLOB_SRC
233
+ ],
234
+ name: 'heyq02/imports/sort',
235
+ ...Object.keys(settings).length > 0 ? {
236
+ settings
237
+ } : {},
238
+ rules: {
239
+ 'import/order': [
240
+ 'error',
241
+ rule
242
+ ]
243
+ }
244
+ });
245
+ }
246
+ return configs;
247
+ };
94
248
  const restrictedSyntaxJs = [
95
249
  'ForInStatement',
96
250
  'LabeledStatement'
@@ -354,6 +508,21 @@ const react = (options = {})=>{
354
508
  if (false !== options.jsxA11y) configs.push(...jsxA11y());
355
509
  return configs;
356
510
  };
511
+ const regexp = ()=>{
512
+ const recommended = eslint_plugin_regexp.configs["flat/recommended"];
513
+ return [
514
+ {
515
+ files: [
516
+ GLOB_SRC
517
+ ],
518
+ name: 'heyq02/regexp',
519
+ plugins: {
520
+ regexp: asESLintPlugin(eslint_plugin_regexp)
521
+ },
522
+ rules: recommended.rules
523
+ }
524
+ ];
525
+ };
357
526
  const rstest = ()=>[
358
527
  {
359
528
  ...rstestPlugin.configs.recommended,
@@ -391,7 +560,9 @@ const specialCases = ()=>[
391
560
  `**/{views,pages,routes,middleware,plugins,api,modules}/${GLOB_SRC}`,
392
561
  `**/{index,vite,esbuild,rollup,rolldown,webpack,rspack,farm,unloader,nuxt,rslint}.${GLOB_SRC_EXT}`,
393
562
  '**/*.d.ts',
394
- '**/.prettierrc*'
563
+ '**/.prettierrc*',
564
+ '**/.oxfmtrc*',
565
+ '**/oxfmt.config.*'
395
566
  ],
396
567
  name: 'heyq02/special/allow-default-export',
397
568
  rules: {
@@ -432,7 +603,7 @@ function resolveTypeScriptOptions(options) {
432
603
  projectService: options.projectService
433
604
  };
434
605
  }
435
- const typescript = (options = true)=>{
606
+ const typescript_typescript = (options = true)=>{
436
607
  const opts = resolveTypeScriptOptions(options);
437
608
  const baseline = opts.strict && opts.typeChecked ? ts.configs.strictTypeChecked : opts.strict ? ts.configs.strict : opts.typeChecked ? ts.configs.recommendedTypeChecked : ts.configs.recommended;
438
609
  const stylistic = opts.stylistic ? opts.typeChecked ? ts.configs.stylisticTypeChecked : ts.configs.stylistic : [];
@@ -568,20 +739,34 @@ const unicorn = ()=>[
568
739
  }
569
740
  }
570
741
  ];
571
- const env_require = createRequire(import.meta.url);
572
- const cwd = node_process.cwd();
573
- function isPackageExists(name) {
574
- try {
575
- env_require.resolve(name, {
576
- paths: [
577
- cwd
578
- ]
579
- });
580
- return true;
581
- } catch {
582
- return false;
583
- }
584
- }
742
+ const unusedVarsOptions = {
743
+ args: 'after-used',
744
+ argsIgnorePattern: '^_',
745
+ ignoreRestSiblings: true,
746
+ varsIgnorePattern: '^_'
747
+ };
748
+ const unusedImports = ()=>[
749
+ {
750
+ files: [
751
+ GLOB_SRC
752
+ ],
753
+ name: 'heyq02/unused-imports',
754
+ plugins: {
755
+ 'unused-imports': asESLintPlugin(eslint_plugin_unused_imports)
756
+ },
757
+ rules: {
758
+ 'no-unused-vars': 'off',
759
+ "@typescript-eslint/no-unused-vars": 'off',
760
+ 'unused-imports/no-unused-imports': 'error',
761
+ 'unused-imports/no-unused-vars': [
762
+ 'error',
763
+ {
764
+ ...unusedVarsOptions
765
+ }
766
+ ]
767
+ }
768
+ }
769
+ ];
585
770
  const hasTypeScript = ()=>isPackageExists("typescript");
586
771
  const hasReact = ()=>isPackageExists('react') || isPackageExists('react-dom') || isPackageExists('next') || isPackageExists('preact') || isPackageExists('@remix-run/react');
587
772
  const hasJest = ()=>isPackageExists('jest') || isPackageExists('@jest/globals');
@@ -614,22 +799,32 @@ const presetJavaScript = ()=>[
614
799
  ...ignores(),
615
800
  ...javascript(),
616
801
  ...imports({
617
- typescript: false
802
+ typescript: false,
803
+ sortImports: true
618
804
  }),
619
805
  ...unicorn(),
620
806
  ...node(),
621
- ...promise()
807
+ ...promise(),
808
+ ...unusedImports(),
809
+ ...regexp(),
810
+ ...deMorgan(),
811
+ ...eslintComments()
622
812
  ];
623
813
  const presetBasic = (typescriptOptions = true)=>[
624
814
  ...ignores(),
625
815
  ...javascript(),
626
- ...typescript(typescriptOptions),
816
+ ...typescript_typescript(typescriptOptions),
627
817
  ...imports({
628
- typescript: true
818
+ typescript: true,
819
+ sortImports: true
629
820
  }),
630
821
  ...unicorn(),
631
822
  ...node(),
632
- ...promise()
823
+ ...promise(),
824
+ ...unusedImports(),
825
+ ...regexp(),
826
+ ...deMorgan(),
827
+ ...eslintComments()
633
828
  ];
634
829
  const presetAll = ()=>heyq02({
635
830
  typescript: true,
@@ -639,14 +834,15 @@ const presetAll = ()=>heyq02({
639
834
  jsxA11y: true
640
835
  });
641
836
  function heyq02(options = {}, ...userConfigs) {
642
- const { ignores: extraIgnores = [], javascript: enableJavaScript = true, typescript: enableTypeScript = hasTypeScript(), unicorn: enableUnicorn = true, imports: enableImports = true, node: enableNode = true, promise: enablePromise = true, react: enableReact = hasReact(), jsxA11y: enableJsxA11y, jest: enableJest = hasJest(), rstest: enableRstest = hasRstest() } = options;
837
+ const { ignores: extraIgnores = [], javascript: enableJavaScript = true, typescript: enableTypeScript = hasTypeScript(), unicorn: enableUnicorn = true, imports: enableImports = true, sortImports: sortImportsOption = true, node: enableNode = true, promise: enablePromise = true, react: enableReact = hasReact(), jsxA11y: enableJsxA11y, jest: enableJest = hasJest(), rstest: enableRstest = hasRstest(), unusedImports: enableUnusedImports = true, regexp: enableRegexp = true, deMorgan: enableDeMorgan = true, eslintComments: enableEslintComments = true } = options;
643
838
  const configs = [
644
839
  ...ignores(extraIgnores)
645
840
  ];
646
841
  if (enableJavaScript) configs.push(...javascript());
647
- if (enableTypeScript) configs.push(...typescript(true === enableTypeScript ? true : enableTypeScript));
842
+ if (enableTypeScript) configs.push(...typescript_typescript(true === enableTypeScript ? true : enableTypeScript));
648
843
  if (enableImports) configs.push(...imports({
649
- typescript: Boolean(enableTypeScript)
844
+ typescript: Boolean(enableTypeScript),
845
+ sortImports: sortImportsOption
650
846
  }));
651
847
  if (enableUnicorn) configs.push(...unicorn());
652
848
  if (enableNode) configs.push(...node(true === enableNode ? 'module' : enableNode));
@@ -657,9 +853,13 @@ function heyq02(options = {}, ...userConfigs) {
657
853
  else if (enableJsxA11y) configs.push(...jsxA11y());
658
854
  if (enableJest) configs.push(...jest());
659
855
  if (enableRstest) configs.push(...rstest());
856
+ if (enableUnusedImports) configs.push(...unusedImports());
857
+ if (enableRegexp) configs.push(...regexp());
858
+ if (enableDeMorgan) configs.push(...deMorgan());
859
+ if (enableEslintComments) configs.push(...eslintComments());
660
860
  configs.push(...specialCases());
661
861
  configs.push(...flattenConfigs(...userConfigs));
662
862
  return configs;
663
863
  }
664
864
  const rslintConfig = heyq02;
665
- export { GLOB_DIST, GLOB_EXCLUDE, GLOB_JS, GLOB_JSX, GLOB_LOCKFILE, GLOB_NODE_MODULES, GLOB_SRC, GLOB_SRC_EXT, GLOB_TESTS, GLOB_TS, GLOB_TSX, flattenConfigs, hasJest, hasReact, hasRstest, hasTypeScript, heyq02, ignores, imports, isInEditorEnv, isInGitHooksOrLintStaged, javascript, jest, jsxA11y, node, presetAll, presetBasic, presetJavaScript, promise, react, restrictedSyntaxJs, rslintConfig, rstest, specialCases, toArray, typescript, unicorn };
865
+ export { DEFAULT_INTERNAL_PATTERN, DEFAULT_SORT_IMPORT_GROUPS, GLOB_DIST, GLOB_EXCLUDE, GLOB_JS, GLOB_JSX, GLOB_LOCKFILE, GLOB_NODE_MODULES, GLOB_SRC, GLOB_SRC_EXT, GLOB_TESTS, GLOB_TS, GLOB_TSX, customGroupsToPathGroups, deMorgan, eslintComments, flattenConfigs, hasJest, hasReact, hasRstest, hasTypeScript, heyq02, ignores, imports, internalPatternToRegex, isInEditorEnv, isInGitHooksOrLintStaged, javascript, jest, jsxA11y, node, presetAll, presetBasic, presetJavaScript, promise, react, regexp, resolveSortImports, restrictedSyntaxJs, rslintConfig, rstest, specialCases, toArray, typescript_typescript as typescript, unicorn, unusedImports };
@@ -0,0 +1,3 @@
1
+ import type { ESLintPlugin } from '@rslint/core';
2
+ /** Cast community ESLint plugin objects into Rslint's `ESLintPlugin` shape. */
3
+ export declare function asESLintPlugin(plugin: unknown): ESLintPlugin;
package/dist/types.d.ts CHANGED
@@ -27,6 +27,91 @@ export interface TypeScriptOptions {
27
27
  projectService?: boolean;
28
28
  }
29
29
  export type NodePreset = boolean | 'module' | 'script' | 'mixed';
30
+ /**
31
+ * `import/order` group names (Rslint / eslint-plugin-import).
32
+ * Closest enforceable equivalent of Oxfmt perfectionist selectors.
33
+ */
34
+ export type SortImportsGroup = 'builtin' | 'external' | 'internal' | 'unknown' | 'parent' | 'sibling' | 'index' | 'object' | 'type';
35
+ /**
36
+ * Oxfmt-like custom group → mapped to `import/order` `pathGroups`.
37
+ *
38
+ * @see https://oxc.rs/docs/guide/usage/formatter/sorting.html
39
+ */
40
+ export interface SortImportsCustomGroup {
41
+ /**
42
+ * Display / documentation name (Oxfmt `groupName`).
43
+ * Not a first-class `import/order` group — attach via {@link group}.
44
+ */
45
+ groupName?: string;
46
+ /**
47
+ * Glob patterns matched against the import source (Oxfmt `elementNamePattern`).
48
+ */
49
+ elementNamePattern: string | string[];
50
+ /**
51
+ * Target `import/order` group to attach to.
52
+ * @default 'external'
53
+ */
54
+ group?: SortImportsGroup;
55
+ /**
56
+ * Position relative to {@link group}.
57
+ * @default 'before'
58
+ */
59
+ position?: 'before' | 'after';
60
+ }
61
+ /**
62
+ * Import sorting options, shaped like Oxfmt `sortImports` and implemented via
63
+ * Rslint `import/order`.
64
+ *
65
+ * Unsupported Oxfmt knobs (no-op / not exposed): per-group `{ newlinesBetween }`,
66
+ * `partitionByComment`, `partitionByNewline`, perfectionist modifier-selectors
67
+ * (`value-builtin`, `type-import`, …).
68
+ *
69
+ * @see https://oxc.rs/docs/guide/usage/formatter/sorting.html
70
+ */
71
+ export interface SortImportsOptions {
72
+ /**
73
+ * Relative order of import groups.
74
+ * @default `['type', ['builtin', 'external'], 'internal', ['parent', 'sibling', 'index'], 'unknown']`
75
+ */
76
+ groups?: (SortImportsGroup | SortImportsGroup[])[];
77
+ /**
78
+ * Insert a blank line between groups.
79
+ * Maps to `newlines-between: 'always' | 'never'`.
80
+ * @default true
81
+ */
82
+ newlinesBetween?: boolean;
83
+ /**
84
+ * Custom path-based groups (Oxfmt `customGroups` → `pathGroups`).
85
+ */
86
+ customGroups?: SortImportsCustomGroup[];
87
+ /**
88
+ * Prefixes treated as internal imports (Oxfmt `internalPattern`).
89
+ * Converted to `settings['import/internal-regex']`.
90
+ * @default `['~/', '@/', '#']`
91
+ */
92
+ internalPattern?: string[];
93
+ /**
94
+ * Alphabetical order within each group.
95
+ * @default 'asc'
96
+ */
97
+ order?: 'asc' | 'desc';
98
+ /**
99
+ * Case-insensitive alphabetical sort.
100
+ * @default true
101
+ */
102
+ ignoreCase?: boolean;
103
+ /**
104
+ * Sort named import / export members.
105
+ * @default true
106
+ */
107
+ named?: boolean;
108
+ /**
109
+ * Whether side-effect imports participate in ordering
110
+ * (`warnOnUnassignedImports`).
111
+ * @default false
112
+ */
113
+ sortSideEffects?: boolean;
114
+ }
30
115
  export interface Options {
31
116
  /**
32
117
  * Global ignore patterns. Merged with the built-in excludes.
@@ -50,6 +135,13 @@ export interface Options {
50
135
  * Import rules. @default true
51
136
  */
52
137
  imports?: boolean;
138
+ /**
139
+ * Sort imports via `import/order`, with an Oxfmt-like options surface.
140
+ * Enabled by default when {@link imports} is on.
141
+ * Set `false` if Oxfmt `sortImports` already owns ordering.
142
+ * @default true
143
+ */
144
+ sortImports?: boolean | SortImportsOptions;
53
145
  /**
54
146
  * Node.js rules.
55
147
  * - `true` / `'module'` — ESM-oriented Node rules (default)
@@ -82,4 +174,24 @@ export interface Options {
82
174
  * @default auto
83
175
  */
84
176
  rstest?: boolean;
177
+ /**
178
+ * Autofix unused imports (`eslint-plugin-unused-imports`).
179
+ * @default true
180
+ */
181
+ unusedImports?: boolean;
182
+ /**
183
+ * RegExp rules (`eslint-plugin-regexp`).
184
+ * @default true
185
+ */
186
+ regexp?: boolean;
187
+ /**
188
+ * De Morgan rewrites (`eslint-plugin-de-morgan`).
189
+ * @default true
190
+ */
191
+ deMorgan?: boolean;
192
+ /**
193
+ * `eslint-disable` hygiene (`@eslint-community/eslint-plugin-eslint-comments`).
194
+ * @default true
195
+ */
196
+ eslintComments?: boolean;
85
197
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@heyq02/rslint-config",
3
- "version": "0.0.2",
3
+ "version": "0.0.3-beta.1",
4
4
  "description": "Opinionated Rslint flat-config preset.",
5
5
  "keywords": [
6
6
  "flat-config",
@@ -23,6 +23,8 @@
23
23
  "dist"
24
24
  ],
25
25
  "type": "module",
26
+ "sideEffects": false,
27
+ "types": "./dist/index.d.ts",
26
28
  "exports": {
27
29
  ".": {
28
30
  "types": "./dist/index.d.ts",
@@ -30,25 +32,30 @@
30
32
  },
31
33
  "./package.json": "./package.json"
32
34
  },
33
- "types": "./dist/index.d.ts",
34
- "sideEffects": false,
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "peerDependencies": {
39
- "@rslint/core": "^0.9.0"
38
+ "dependencies": {
39
+ "@eslint-community/eslint-plugin-eslint-comments": "^4.8.1",
40
+ "eslint-plugin-de-morgan": "^2.2.0",
41
+ "eslint-plugin-regexp": "^3.3.1",
42
+ "eslint-plugin-unused-imports": "^4.4.1",
43
+ "local-pkg": "^1.2.1"
40
44
  },
41
45
  "devDependencies": {
46
+ "@arethetypeswrong/cli": "^0.18.5",
42
47
  "@heyq02/rslib-config": "^1.0.1",
43
48
  "@rslib/core": "^1.0.3",
44
49
  "@rslint/core": "^0.9.4",
45
50
  "@types/node": "^26.1.1",
51
+ "automd": "^0.4.3",
46
52
  "bumpp": "^11.1.0",
47
- "prettier": "^3.9.6",
48
- "typescript": "^7.0.2"
53
+ "publint": "^0.3.25",
54
+ "typescript": "^7.0.2",
55
+ "vite-plus": "^1.1.0"
49
56
  },
50
- "engines": {
51
- "node": "^20.19.0 || >=22.12.0"
57
+ "peerDependencies": {
58
+ "@rslint/core": "^0.9.0"
52
59
  },
53
60
  "devEngines": {
54
61
  "packageManager": {
@@ -57,14 +64,23 @@
57
64
  "onFail": "download"
58
65
  }
59
66
  },
67
+ "engines": {
68
+ "node": "^20.19.0 || >=22.12.0"
69
+ },
60
70
  "scripts": {
61
71
  "build": "rslib",
62
72
  "dev": "rslib --watch",
63
- "test": "echo Skip",
73
+ "test": "vp test",
64
74
  "lint": "rslint .",
65
75
  "lint:fix": "rslint --fix .",
66
- "format": "prettier --write .",
76
+ "automd": "automd",
77
+ "format": "pnpm automd && vp fmt",
78
+ "format:check": "vp fmt --check",
67
79
  "typecheck": "tsc --noEmit",
80
+ "check": "pnpm run format:check && pnpm run lint && pnpm run typecheck",
81
+ "publint": "publint",
82
+ "attw": "pnpm pack && attw heyq02-rslint-config-*.tgz --profile esm-only && rm -f heyq02-rslint-config-*.tgz",
83
+ "check:package": "pnpm run build && pnpm run publint && pnpm run attw",
68
84
  "release": "bumpp"
69
85
  }
70
86
  }