@maz-ui/eslint-config 5.0.0-beta.0 → 5.0.0-beta.11

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,15 +1,17 @@
1
1
  # @maz-ui/eslint-config
2
2
 
3
- Reusable ESLint configuration for JavaScript/TypeScript projects.
3
+ Reusable ESLint configuration for JavaScript/TypeScript projects, built on top of [`@antfu/eslint-config`](https://github.com/antfu/eslint-config).
4
4
 
5
5
  ## Features
6
6
 
7
- - 🚀 **Based on @antfu/eslint-config** - Modern and performant configuration
8
- - 🛡️ **Strict TypeScript** - Full TypeScript support
9
- - 🎨 **Tailwind CSS** - Rules for Tailwind CSS (with Tailwind CSS plugin)
10
- - 🔍 **SonarJS** - Code quality with SonarJS (with SonarJS plugin)
11
- - ⚙️ **Configurable** - Flexible and extensible options
12
- - 📦 **Production ready** - Optimized for production environments
7
+ - 🚀 **Based on @antfu/eslint-config** modern, performant, flat-config first
8
+ - 🛡️ **TypeScript-first** strict mode, sensible defaults
9
+ - 🎨 **Tailwind CSS v3 + v4** — via [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss) (compat ESLint 7→10, both Tailwind versions)
10
+ - 🧩 **Custom `maz/` rules** ships its own ESLint plugin namespaced `maz/*` (see [Custom rules](#custom-rules))
11
+ - 🔍 **SonarJS** code quality / cognitive complexity
12
+ - **Vue accessibility** — opt-in `eslint-plugin-vuejs-accessibility`
13
+ - 📐 **Formatters** — Prettier-style formatting via `eslint-plugin-format`
14
+ - ⚙️ **Configurable** — pick presets, add per-file rules, custom ignore globs
13
15
 
14
16
  ## Installation
15
17
 
@@ -26,139 +28,203 @@ import { defineConfig } from '@maz-ui/eslint-config'
26
28
  export default defineConfig()
27
29
  ```
28
30
 
29
- ## Custom configuration
31
+ Vue / Nuxt are auto-detected from your `package.json`.
32
+
33
+ ## Configuration
30
34
 
31
35
  ```js
32
36
  // eslint.config.js
33
37
  import { defineConfig } from '@maz-ui/eslint-config'
34
38
 
35
39
  export default defineConfig({
36
- // Environment (affects console rules)
37
- env: 'production', // 'development' | 'production'
40
+ env: 'production', // 'development' | 'production' — affects no-console / no-debugger severity
38
41
 
39
- // Enable/disable plugins
42
+ // Antfu options (forwarded as-is)
40
43
  typescript: true,
41
- tailwindcss: true,
42
- sonarjs: true,
43
44
  formatters: true,
45
+ unicorn: true,
46
+
47
+ // Maz-UI options
48
+ sonarjs: true,
49
+ vueAccessibility: false,
50
+ tailwindcss: 'recommended', // see "Tailwind support" below
44
51
 
45
- // Files to ignore
52
+ // Extra ignore globs (merged with defaults)
46
53
  ignores: ['custom-dist/**'],
47
54
 
48
55
  // Custom rules
49
56
  rules: {
50
57
  'no-console': 'error',
51
- 'prefer-const': 'warn'
52
- }
58
+ },
59
+
60
+ // 'silent' | 'default' | 'debug' | 'verbose' — see "Logging" below.
61
+ logLevel: 'default',
53
62
  })
54
63
  ```
55
64
 
56
- ## Available options
57
-
58
- ```typescript
59
- interface MazESLintOptions {
60
- typescript?: boolean // TypeScript support (default: true)
61
- tailwindcss?: boolean // Tailwind CSS rules (default: true)
62
- sonarjs?: boolean // SonarJS rules (default: true)
63
- formatters?: boolean // Formatters support (default: true)
64
- env?: 'development' | 'production' // Environment (default: 'development')
65
- ignores?: string[] // Files to ignore
66
- rules?: Record<string, any> // Custom ESLint rules
67
- }
68
- ```
65
+ ## Logging
69
66
 
70
- ## Advanced usage
67
+ The preset prints a titled box summarizing what it resolved (typescript, vue, tailwindcss preset, sonarjs, …). Powered by `@maz-ui/node`'s consola-based logger.
71
68
 
72
- ### Selective rule imports
69
+ | Value | What you see |
70
+ | --------------------------- | -------------------------------------------------- |
71
+ | `'silent'` | Nothing. |
72
+ | `'default'` _(TTY default)_ | Titled box of resolved feature toggles. |
73
+ | `'debug'` | Above + each plugin / preset / overrides addition. |
74
+ | `'verbose'` | Above + final block count and ignore-glob count. |
73
75
 
74
- ```js
75
- import { baseRules, sonarjsRules, tailwindcssRules } from '@maz-ui/eslint-config'
76
+ The default is **`'default'` in interactive terminals, `'silent'` otherwise** (CI, pipes, JSON / SARIF formatters). This avoids polluting machine-readable ESLint output. Override by passing `logLevel` explicitly.
76
77
 
77
- export default [
78
- {
79
- rules: {
80
- ...baseRules,
81
- ...sonarjsRules,
82
- // Your custom rules
83
- }
84
- }
85
- ]
86
- ```
78
+ ## Tailwind support
87
79
 
88
- ### Example for Vue project
80
+ Powered by [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss) compatible with ESLint 7→10 and Tailwind v3 + v4.
89
81
 
90
- ```js
91
- // eslint.config.js
92
- import { defineConfig } from '@maz-ui/eslint-config'
82
+ | Value | Behavior |
83
+ | ----------------------- | ----------------------------------------------------------------------- |
84
+ | `false` _(default)_ | Disabled. |
85
+ | `true` | Same as `'recommended'` with default settings. |
86
+ | `'recommended'` | Stylistic + correctness rules. |
87
+ | `'stylistic'` | Formatting rules only (class order, line wrapping, …). |
88
+ | `'correctness'` | Validation rules only (`no-unknown-classes`, `no-conflicting-classes`). |
89
+ | `MazTailwindcssOptions` | Pick a preset and pass plugin settings. |
93
90
 
94
- export default defineConfig({
95
- env: 'production',
96
- rules: {
97
- 'vue/custom-event-name-casing': ['error', 'kebab-case'],
98
- 'vue/no-undef-components': ['error', {
99
- ignorePatterns: ['RouterView', 'RouterLink']
100
- }]
101
- }
91
+ ```js
92
+ defineConfig({
93
+ tailwindcss: {
94
+ preset: 'recommended',
95
+ // Tailwind v4: path to the CSS that does `@import "tailwindcss" prefix(maz)`
96
+ entryPoint: 'src/main.css',
97
+ // Tailwind v3 instead:
98
+ // tailwindConfig: 'tailwind.config.ts',
99
+ detectComponentClasses: true,
100
+ // Custom `maz/tailwind-no-arbitrary-px` rule — see "Custom rules" below.
101
+ noArbitraryPx: true,
102
+ },
102
103
  })
103
104
  ```
104
105
 
105
- ## Included rules
106
+ > [!IMPORTANT]
107
+ > The plugin's `no-unknown-classes` rule needs to know about your Tailwind setup (prefix, custom utilities, theme tokens). Without `entryPoint` (v4) or `tailwindConfig` (v3), it falls back to the default Tailwind config and will flood with false positives in projects that customize either. **Pass the path explicitly** if your Tailwind setup is non-standard.
106
108
 
107
- ### Base (JavaScript/TypeScript)
109
+ ## Custom rules
108
110
 
109
- - Console management by environment
110
- - Code quality rules
111
- - Optimized TypeScript support
111
+ This preset ships its own ESLint plugin under the `maz/*` namespace. Rules are organized by category — `maz/tailwind-*` for Tailwind-specific rules, ready for `maz/js-*` and others as they get added.
112
112
 
113
- ### SonarJS
113
+ ### `maz/tailwind-no-arbitrary-px`
114
114
 
115
- - Limited cognitive complexity
116
- - Duplicate code detection
117
- - Security best practices
115
+ Forbids `px` units inside Tailwind arbitrary value classes (`w-[16px]`, `m-[-16px]`, `[gap:24px]`, …) and **autofixes** them to `rem` (or `em`) using the configured root font-size. Whitespace inside brackets (e.g. `[up to 100px]`) is left alone so plain prose is never rewritten.
118
116
 
119
- ### Tailwind CSS
117
+ > [!NOTE]
118
+ > The rule and the `maz` plugin are only registered when `tailwindcss` is enabled (any value other than `false`). With `tailwindcss: false` _(default)_ neither the plugin nor the rule appear in the final flat-config. To use the rule in a project that doesn't opt into the full Tailwind preset, register it manually (see [Use the plugin directly](#use-the-plugin-directly)).
120
119
 
121
- - Consistent class ordering
122
- - Contradictory class detection
123
- - Valid Tailwind syntax
120
+ When `tailwindcss` is on, the rule is enabled with defaults. You can tune it either way:
124
121
 
125
- ## Compatibility
122
+ **Standard ESLint override** _(idiomatic, max control)_
126
123
 
127
- - **ESLint** ^9.0.0
128
- - **Node.js** >=18.0.0
129
- - **TypeScript** ^5.0.0
124
+ ```js
125
+ defineConfig({
126
+ tailwindcss: 'recommended',
127
+ rules: {
128
+ 'maz/tailwind-no-arbitrary-px': ['error', { baseFontSize: 10, unit: 'em' }],
129
+ },
130
+ })
131
+ ```
130
132
 
131
- ## Usage examples
133
+ User `rules` overrides are applied in a _trailing_ block, so they win over the rule's defaults wired in by `tailwindcssConfigs`.
132
134
 
133
- ### Simple JavaScript project
135
+ **Ergonomic shortcut** _(set the defaults via `tailwindcss.noArbitraryPx`)_
134
136
 
135
137
  ```js
136
- export default defineConfig({
137
- typescript: false,
138
- tailwindcss: false
138
+ defineConfig({
139
+ tailwindcss: {
140
+ preset: 'recommended',
141
+ noArbitraryPx: {
142
+ baseFontSize: 16, // default — convert px / 16 → rem
143
+ unit: 'rem', // default — target unit ('rem' | 'em')
144
+ severity: 'error', // default — 'off' | 'warn' | 'error'
145
+ },
146
+ },
139
147
  })
140
148
  ```
141
149
 
142
- ### Project with Tailwind
150
+ | `noArbitraryPx` value | Behavior |
151
+ | ---------------------------------- | -------------------------------------------------- |
152
+ | `true` _(default)_ | Enabled with defaults (`baseFontSize: 16`, `rem`). |
153
+ | `false` | Disabled. |
154
+ | `{ baseFontSize, unit, severity }` | Override any subset of the defaults. |
155
+
156
+ If both are set, the standard `rules` override wins (last block applied).
157
+
158
+ Examples after autofix (with `baseFontSize: 16`):
159
+
160
+ | Before | After |
161
+ | --------------------- | ----------------------- |
162
+ | `w-[16px]` | `w-[1rem]` |
163
+ | `m-[-16px]` | `m-[-1rem]` |
164
+ | `tracking-[.5px]` | `tracking-[0.03125rem]` |
165
+ | `p-[16px_8px]` | `p-[1rem_0.5rem]` |
166
+ | `[gap:24px]` | `[gap:1.5rem]` |
167
+ | `w-[calc(100%-16px)]` | `w-[calc(100%-1rem)]` |
168
+
169
+ ### Use the plugin directly
170
+
171
+ If you don't want the full Tailwind preset (or just prefer wiring rules yourself), import `mazPlugin` and register it in your own flat-config block. This is also the way to use `maz/tailwind-*` rules without enabling `tailwindcss` in `defineConfig`:
143
172
 
144
173
  ```js
145
- export default defineConfig({
146
- tailwindcss: true,
174
+ import { mazPlugin } from '@maz-ui/eslint-config'
175
+
176
+ export default [{
177
+ files: ['**/*.{ts,tsx,vue}'],
178
+ plugins: { maz: mazPlugin },
147
179
  rules: {
148
- 'tailwindcss/classnames-order': 'error'
149
- }
150
- })
180
+ 'maz/tailwind-no-arbitrary-px': ['error', { baseFontSize: 16 }],
181
+ },
182
+ }]
151
183
  ```
152
184
 
153
- ### Production project
185
+ ## Advanced
186
+
187
+ ### Compose your own config
154
188
 
155
189
  ```js
156
- export default defineConfig({
157
- env: 'production',
158
- sonarjs: true,
190
+ import {
191
+ baseRules,
192
+ mazPlugin,
193
+ sonarjsRules,
194
+ tailwindcssConfigs,
195
+ vueRules,
196
+ } from '@maz-ui/eslint-config'
197
+
198
+ export default [
199
+ {
200
+ plugins: { maz: mazPlugin },
201
+ rules: {
202
+ ...baseRules(true), // production = true
203
+ ...sonarjsRules,
204
+ ...vueRules,
205
+ 'maz/tailwind-no-arbitrary-px': 'error',
206
+ },
207
+ },
208
+ ...tailwindcssConfigs('stylistic', {
209
+ entryPoint: 'src/main.css',
210
+ noArbitraryPx: { unit: 'em' },
211
+ }),
212
+ ]
213
+ ```
214
+
215
+ ### Vue project
216
+
217
+ ```js
218
+ defineConfig({
219
+ vue: true, // auto-detected if `vue` / `nuxt` is in deps
220
+ tailwindcss: 'recommended',
159
221
  rules: {
160
- 'no-console': 'error',
161
- 'no-debugger': 'error'
162
- }
222
+ 'vue/custom-event-name-casing': ['error', 'kebab-case'],
223
+ },
163
224
  })
164
225
  ```
226
+
227
+ ## Compatibility
228
+
229
+ - **ESLint** `^9.0.0 || ^10.0.0`
230
+ - **Node.js** `>=20.19.0`
package/dist/index.d.mts CHANGED
@@ -1,13 +1,102 @@
1
1
  import { antfu, OptionsConfig, Rules, TypedFlatConfigItem } from '@antfu/eslint-config';
2
- import { Linter } from 'eslint';
2
+ import { Linter, ESLint, Rule } from 'eslint';
3
+
4
+ /**
5
+ * Mirror of `@maz-ui/node`'s LogLevel — kept inline here to avoid making this
6
+ * preset depend on `@maz-ui/node`, which would create a build cycle (the
7
+ * `eslint.config.ts` of `@maz-ui/node` imports `@maz-ui/eslint-config`, which
8
+ * itself would depend on `@maz-ui/node` for the same logger). Direct usage of
9
+ * `consola` keeps the dependency graph acyclic.
10
+ */
11
+ type LogLevel = 'silent' | 'error' | 'warning' | 'normal' | 'default' | 'debug' | 'trace' | 'verbose';
3
12
 
4
13
  type MazESLintUserConfig = TypedFlatConfigItem | TypedFlatConfigItem[] | Linter.Config | Linter.Config[];
14
+ type TailwindcssPreset = 'recommended' | 'stylistic' | 'correctness';
15
+ /**
16
+ * Settings forwarded to `eslint-plugin-better-tailwindcss` under
17
+ * `settings['better-tailwindcss']`. See the plugin docs for the canonical
18
+ * meaning of each field.
19
+ */
20
+ interface MazTailwindcssOptions {
21
+ /**
22
+ * Which preset to load. Each one comes in `*-warn` and `*-error` flavors
23
+ * (defaults match the plugin's defaults: stylistic→warn, correctness→error).
24
+ *
25
+ * @default 'recommended'
26
+ */
27
+ preset?: TailwindcssPreset;
28
+ /**
29
+ * Tailwind v4: path to the CSS entry file (the one that does
30
+ * `@import "tailwindcss"`, possibly with `prefix(...)`). Required for the
31
+ * plugin to know about your prefix and `@theme` tokens.
32
+ */
33
+ entryPoint?: string;
34
+ /**
35
+ * Tailwind v3: path to `tailwind.config.{js,ts,cjs,mjs}`. Use `entryPoint`
36
+ * for v4 instead.
37
+ */
38
+ tailwindConfig?: string;
39
+ /**
40
+ * Tailwind v4: enable detection of custom component classes (`@layer
41
+ * components { … }`) so they are not reported as unknown.
42
+ *
43
+ * @default false
44
+ */
45
+ detectComponentClasses?: boolean;
46
+ /**
47
+ * Working directory used to resolve `tailwindcss` and the config files
48
+ * above. Useful in monorepos when ESLint runs from the repo root.
49
+ */
50
+ cwd?: string;
51
+ /**
52
+ * Path to a `tsconfig.json` used to resolve `paths` aliases inside the
53
+ * Tailwind config.
54
+ */
55
+ tsconfig?: string;
56
+ /**
57
+ * Configure the `maz/tailwind-no-arbitrary-px` rule (forbids `px`
58
+ * values inside Tailwind arbitrary value classes such as `w-[16px]`).
59
+ *
60
+ * - `false`: disable the rule.
61
+ * - `true` *(default)*: enable with defaults (`baseFontSize: 16`,
62
+ * `unit: 'rem'`).
63
+ * - Object: enable with custom options.
64
+ */
65
+ noArbitraryPx?: boolean | {
66
+ /**
67
+ * Root font size, in pixels, used to convert `Npx` to `rem`/`em`.
68
+ *
69
+ * @default 16
70
+ */
71
+ baseFontSize?: number;
72
+ /**
73
+ * Output unit for the autofix.
74
+ *
75
+ * @default 'rem'
76
+ */
77
+ unit?: 'rem' | 'em';
78
+ /**
79
+ * ESLint severity for the rule.
80
+ *
81
+ * @default 'error'
82
+ */
83
+ severity?: 'off' | 'warn' | 'error';
84
+ };
85
+ }
5
86
  interface MazESLintOptions extends OptionsConfig {
6
87
  /**
7
- * Enable Tailwind CSS support
88
+ * Tailwind CSS support — wires `eslint-plugin-better-tailwindcss`.
89
+ *
90
+ * - `false` *(default)*: disabled.
91
+ * - `true`: load the `'recommended'` preset with default settings.
92
+ * - `'recommended' | 'stylistic' | 'correctness'`: pick a preset, default
93
+ * settings.
94
+ * - `MazTailwindcssOptions`: pick a preset and pass settings (e.g.
95
+ * `entryPoint` so the plugin learns your v4 prefix).
96
+ *
8
97
  * @default false
9
98
  */
10
- tailwindcss?: boolean;
99
+ tailwindcss?: boolean | TailwindcssPreset | MazTailwindcssOptions;
11
100
  /**
12
101
  * Enable SonarJS rules for code quality
13
102
  * @default true
@@ -32,6 +121,19 @@ interface MazESLintOptions extends OptionsConfig {
32
121
  * Additional rules to merge
33
122
  */
34
123
  rules?: Partial<Rules>;
124
+ /**
125
+ * Verbosity of the logs emitted while resolving the config. Defaults to
126
+ * `'default'` so the resolved-configuration box is shown when the preset
127
+ * loads. Set to `'silent'` to hide it, or `'debug'` / `'verbose'` to dig
128
+ * deeper.
129
+ *
130
+ * - `'silent'`: nothing is printed.
131
+ * - `'default'` *(default)*: a titled box summarizing the resolved feature
132
+ * toggles.
133
+ * - `'debug'`: also logs each plugin / preset / overrides addition.
134
+ * - `'verbose'`: also logs the final shape (config block count, etc.).
135
+ */
136
+ logLevel?: LogLevel;
35
137
  }
36
138
  type MazESLintConfig = ReturnType<typeof antfu>;
37
139
 
@@ -68,24 +170,52 @@ declare const sonarjsTestRules: {
68
170
  };
69
171
 
70
172
  /**
71
- * Tailwind CSS ESLint rules configuration.
72
- *
73
- * These rules only activate when the user opts in via
74
- * `defineMazEslintConfig({ tailwindcss: true })` AND is on ESLint 9 +
75
- * Tailwind v3. The plugin itself does not yet support ESLint 10+ or
76
- * Tailwind v4 (upstream — uses the removed context.getSourceCode API).
173
+ * Files where Tailwind classes commonly live. Exposed for users who want to
174
+ * compose their own config.
77
175
  */
78
- declare const tailwindcssRules: {
79
- readonly 'tailwindcss/no-custom-classname': "off";
80
- readonly 'tailwindcss/classnames-order': "warn";
81
- readonly 'tailwindcss/no-contradicting-classname': "error";
82
- readonly 'tailwindcss/enforces-negative-arbitrary-values': "error";
83
- readonly 'tailwindcss/enforces-shorthand': "warn";
84
- };
176
+ declare const TAILWINDCSS_DEFAULT_FILES: string[];
177
+ /**
178
+ * Build the flat-config block(s) that wire up
179
+ * `eslint-plugin-better-tailwindcss` AND the custom `maz/tailwind-*`
180
+ * rules shipped by this package. The plugin already ships preset
181
+ * objects with `plugins` + `rules`; we spread them and add `files` plus
182
+ * `settings['better-tailwindcss']` so the user's options reach the
183
+ * plugin.
184
+ */
185
+ declare function tailwindcssConfigs(preset: TailwindcssPreset, settings: MazTailwindcssOptions): Linter.Config[];
85
186
 
86
187
  declare const vueRules: {
87
188
  'vue/custom-event-name-casing': ["error", "kebab-case"];
88
189
  };
190
+ declare const vueSfcOnlyRules: {
191
+ 'no-useless-assignment': "off";
192
+ };
193
+
194
+ /**
195
+ * The `maz` ESLint plugin bundling all custom rules shipped by
196
+ * `@maz-ui/eslint-config`. Rules are namespaced by category:
197
+ *
198
+ * - `maz/tailwind-*` — Tailwind-specific rules
199
+ * - `maz/js-*` — general JS/TS rules (when added)
200
+ * - …
201
+ *
202
+ * The plugin is auto-wired by the relevant config block in
203
+ * `src/configs/*.ts`, but it is also exported so users can compose
204
+ * their own flat-config setup.
205
+ */
206
+ declare const mazPlugin: ESLint.Plugin;
207
+
208
+ /**
209
+ * Registry of all custom rules shipped by `@maz-ui/eslint-config`.
210
+ *
211
+ * Keys here are the final rule names exposed by the `maz` plugin, so a
212
+ * file in `src/rules/tailwind/no-arbitrary-px.ts` becomes
213
+ * `maz/tailwind-no-arbitrary-px` when wired through the plugin.
214
+ *
215
+ * Add new rules by dropping a file in the matching category folder
216
+ * (`tailwind/`, `js/`, …) and registering it below.
217
+ */
218
+ declare const rules: Record<string, Rule.RuleModule>;
89
219
 
90
220
  /**
91
221
  * Create ESLint configuration for Maz-UI and JavaScript/TypeScript projects
@@ -107,5 +237,5 @@ declare const vueRules: {
107
237
  */
108
238
  declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
109
239
 
110
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
111
- export type { MazESLintConfig, MazESLintOptions };
240
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, mazPlugin, rules as mazRules, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
241
+ export type { MazESLintConfig, MazESLintOptions, MazTailwindcssOptions, TailwindcssPreset };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,102 @@
1
1
  import { antfu, OptionsConfig, Rules, TypedFlatConfigItem } from '@antfu/eslint-config';
2
- import { Linter } from 'eslint';
2
+ import { Linter, ESLint, Rule } from 'eslint';
3
+
4
+ /**
5
+ * Mirror of `@maz-ui/node`'s LogLevel — kept inline here to avoid making this
6
+ * preset depend on `@maz-ui/node`, which would create a build cycle (the
7
+ * `eslint.config.ts` of `@maz-ui/node` imports `@maz-ui/eslint-config`, which
8
+ * itself would depend on `@maz-ui/node` for the same logger). Direct usage of
9
+ * `consola` keeps the dependency graph acyclic.
10
+ */
11
+ type LogLevel = 'silent' | 'error' | 'warning' | 'normal' | 'default' | 'debug' | 'trace' | 'verbose';
3
12
 
4
13
  type MazESLintUserConfig = TypedFlatConfigItem | TypedFlatConfigItem[] | Linter.Config | Linter.Config[];
14
+ type TailwindcssPreset = 'recommended' | 'stylistic' | 'correctness';
15
+ /**
16
+ * Settings forwarded to `eslint-plugin-better-tailwindcss` under
17
+ * `settings['better-tailwindcss']`. See the plugin docs for the canonical
18
+ * meaning of each field.
19
+ */
20
+ interface MazTailwindcssOptions {
21
+ /**
22
+ * Which preset to load. Each one comes in `*-warn` and `*-error` flavors
23
+ * (defaults match the plugin's defaults: stylistic→warn, correctness→error).
24
+ *
25
+ * @default 'recommended'
26
+ */
27
+ preset?: TailwindcssPreset;
28
+ /**
29
+ * Tailwind v4: path to the CSS entry file (the one that does
30
+ * `@import "tailwindcss"`, possibly with `prefix(...)`). Required for the
31
+ * plugin to know about your prefix and `@theme` tokens.
32
+ */
33
+ entryPoint?: string;
34
+ /**
35
+ * Tailwind v3: path to `tailwind.config.{js,ts,cjs,mjs}`. Use `entryPoint`
36
+ * for v4 instead.
37
+ */
38
+ tailwindConfig?: string;
39
+ /**
40
+ * Tailwind v4: enable detection of custom component classes (`@layer
41
+ * components { … }`) so they are not reported as unknown.
42
+ *
43
+ * @default false
44
+ */
45
+ detectComponentClasses?: boolean;
46
+ /**
47
+ * Working directory used to resolve `tailwindcss` and the config files
48
+ * above. Useful in monorepos when ESLint runs from the repo root.
49
+ */
50
+ cwd?: string;
51
+ /**
52
+ * Path to a `tsconfig.json` used to resolve `paths` aliases inside the
53
+ * Tailwind config.
54
+ */
55
+ tsconfig?: string;
56
+ /**
57
+ * Configure the `maz/tailwind-no-arbitrary-px` rule (forbids `px`
58
+ * values inside Tailwind arbitrary value classes such as `w-[16px]`).
59
+ *
60
+ * - `false`: disable the rule.
61
+ * - `true` *(default)*: enable with defaults (`baseFontSize: 16`,
62
+ * `unit: 'rem'`).
63
+ * - Object: enable with custom options.
64
+ */
65
+ noArbitraryPx?: boolean | {
66
+ /**
67
+ * Root font size, in pixels, used to convert `Npx` to `rem`/`em`.
68
+ *
69
+ * @default 16
70
+ */
71
+ baseFontSize?: number;
72
+ /**
73
+ * Output unit for the autofix.
74
+ *
75
+ * @default 'rem'
76
+ */
77
+ unit?: 'rem' | 'em';
78
+ /**
79
+ * ESLint severity for the rule.
80
+ *
81
+ * @default 'error'
82
+ */
83
+ severity?: 'off' | 'warn' | 'error';
84
+ };
85
+ }
5
86
  interface MazESLintOptions extends OptionsConfig {
6
87
  /**
7
- * Enable Tailwind CSS support
88
+ * Tailwind CSS support — wires `eslint-plugin-better-tailwindcss`.
89
+ *
90
+ * - `false` *(default)*: disabled.
91
+ * - `true`: load the `'recommended'` preset with default settings.
92
+ * - `'recommended' | 'stylistic' | 'correctness'`: pick a preset, default
93
+ * settings.
94
+ * - `MazTailwindcssOptions`: pick a preset and pass settings (e.g.
95
+ * `entryPoint` so the plugin learns your v4 prefix).
96
+ *
8
97
  * @default false
9
98
  */
10
- tailwindcss?: boolean;
99
+ tailwindcss?: boolean | TailwindcssPreset | MazTailwindcssOptions;
11
100
  /**
12
101
  * Enable SonarJS rules for code quality
13
102
  * @default true
@@ -32,6 +121,19 @@ interface MazESLintOptions extends OptionsConfig {
32
121
  * Additional rules to merge
33
122
  */
34
123
  rules?: Partial<Rules>;
124
+ /**
125
+ * Verbosity of the logs emitted while resolving the config. Defaults to
126
+ * `'default'` so the resolved-configuration box is shown when the preset
127
+ * loads. Set to `'silent'` to hide it, or `'debug'` / `'verbose'` to dig
128
+ * deeper.
129
+ *
130
+ * - `'silent'`: nothing is printed.
131
+ * - `'default'` *(default)*: a titled box summarizing the resolved feature
132
+ * toggles.
133
+ * - `'debug'`: also logs each plugin / preset / overrides addition.
134
+ * - `'verbose'`: also logs the final shape (config block count, etc.).
135
+ */
136
+ logLevel?: LogLevel;
35
137
  }
36
138
  type MazESLintConfig = ReturnType<typeof antfu>;
37
139
 
@@ -68,24 +170,52 @@ declare const sonarjsTestRules: {
68
170
  };
69
171
 
70
172
  /**
71
- * Tailwind CSS ESLint rules configuration.
72
- *
73
- * These rules only activate when the user opts in via
74
- * `defineMazEslintConfig({ tailwindcss: true })` AND is on ESLint 9 +
75
- * Tailwind v3. The plugin itself does not yet support ESLint 10+ or
76
- * Tailwind v4 (upstream — uses the removed context.getSourceCode API).
173
+ * Files where Tailwind classes commonly live. Exposed for users who want to
174
+ * compose their own config.
77
175
  */
78
- declare const tailwindcssRules: {
79
- readonly 'tailwindcss/no-custom-classname': "off";
80
- readonly 'tailwindcss/classnames-order': "warn";
81
- readonly 'tailwindcss/no-contradicting-classname': "error";
82
- readonly 'tailwindcss/enforces-negative-arbitrary-values': "error";
83
- readonly 'tailwindcss/enforces-shorthand': "warn";
84
- };
176
+ declare const TAILWINDCSS_DEFAULT_FILES: string[];
177
+ /**
178
+ * Build the flat-config block(s) that wire up
179
+ * `eslint-plugin-better-tailwindcss` AND the custom `maz/tailwind-*`
180
+ * rules shipped by this package. The plugin already ships preset
181
+ * objects with `plugins` + `rules`; we spread them and add `files` plus
182
+ * `settings['better-tailwindcss']` so the user's options reach the
183
+ * plugin.
184
+ */
185
+ declare function tailwindcssConfigs(preset: TailwindcssPreset, settings: MazTailwindcssOptions): Linter.Config[];
85
186
 
86
187
  declare const vueRules: {
87
188
  'vue/custom-event-name-casing': ["error", "kebab-case"];
88
189
  };
190
+ declare const vueSfcOnlyRules: {
191
+ 'no-useless-assignment': "off";
192
+ };
193
+
194
+ /**
195
+ * The `maz` ESLint plugin bundling all custom rules shipped by
196
+ * `@maz-ui/eslint-config`. Rules are namespaced by category:
197
+ *
198
+ * - `maz/tailwind-*` — Tailwind-specific rules
199
+ * - `maz/js-*` — general JS/TS rules (when added)
200
+ * - …
201
+ *
202
+ * The plugin is auto-wired by the relevant config block in
203
+ * `src/configs/*.ts`, but it is also exported so users can compose
204
+ * their own flat-config setup.
205
+ */
206
+ declare const mazPlugin: ESLint.Plugin;
207
+
208
+ /**
209
+ * Registry of all custom rules shipped by `@maz-ui/eslint-config`.
210
+ *
211
+ * Keys here are the final rule names exposed by the `maz` plugin, so a
212
+ * file in `src/rules/tailwind/no-arbitrary-px.ts` becomes
213
+ * `maz/tailwind-no-arbitrary-px` when wired through the plugin.
214
+ *
215
+ * Add new rules by dropping a file in the matching category folder
216
+ * (`tailwind/`, `js/`, …) and registering it below.
217
+ */
218
+ declare const rules: Record<string, Rule.RuleModule>;
89
219
 
90
220
  /**
91
221
  * Create ESLint configuration for Maz-UI and JavaScript/TypeScript projects
@@ -107,5 +237,5 @@ declare const vueRules: {
107
237
  */
108
238
  declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
109
239
 
110
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
111
- export type { MazESLintConfig, MazESLintOptions };
240
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, mazPlugin, rules as mazRules, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
241
+ export type { MazESLintConfig, MazESLintOptions, MazTailwindcssOptions, TailwindcssPreset };
package/dist/index.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { readFileSync } from 'node:fs';
2
- import { createRequire } from 'node:module';
3
2
  import { join } from 'node:path';
4
3
  import antfu from '@antfu/eslint-config';
5
4
  import { configs } from 'eslint-plugin-sonarjs';
6
5
  import vueA11y from 'eslint-plugin-vuejs-accessibility';
6
+ import { createConsola } from 'consola';
7
+ import betterTailwindcss from 'eslint-plugin-better-tailwindcss';
7
8
 
8
9
  function baseRules(isProduction) {
9
10
  return {
@@ -71,6 +72,29 @@ const GLOBAL_IGNORES = [
71
72
  "**/components.d.ts"
72
73
  ];
73
74
 
75
+ const LEVELS = {
76
+ silent: Number.NEGATIVE_INFINITY,
77
+ error: 0,
78
+ warning: 1,
79
+ normal: 2,
80
+ default: 3,
81
+ debug: 4,
82
+ trace: 5,
83
+ verbose: Number.POSITIVE_INFINITY
84
+ };
85
+ function createLogger() {
86
+ const consola = createConsola();
87
+ return {
88
+ setLevel: (level) => {
89
+ consola.level = LEVELS[level];
90
+ },
91
+ info: (msg) => consola.info(msg),
92
+ debug: (msg) => consola.debug(msg),
93
+ verbose: (msg) => consola.verbose(msg),
94
+ box: (opts) => consola.box(opts)
95
+ };
96
+ }
97
+
74
98
  const markdown = {
75
99
  files: ["**/*.md/**"],
76
100
  rules: {
@@ -103,18 +127,167 @@ const sonarjsTestRules = {
103
127
  "sonarjs/no-duplicate-string": "off"
104
128
  };
105
129
 
106
- const tailwindcssRules = {
107
- // Allow custom class names (useful for component libraries)
108
- "tailwindcss/no-custom-classname": "off",
109
- // Enforce consistent class ordering
110
- "tailwindcss/classnames-order": "warn",
111
- // Prevent contradicting classes
112
- "tailwindcss/no-contradicting-classname": "error",
113
- // Enforce valid Tailwind syntax
114
- "tailwindcss/enforces-negative-arbitrary-values": "error",
115
- "tailwindcss/enforces-shorthand": "warn"
130
+ const DEFAULT_BASE_FONT_SIZE = 16;
131
+ const DEFAULT_UNIT = "rem";
132
+ const ARBITRARY_BRACKET = /\[([^\s\]]+)\]/g;
133
+ const HAS_PX_VALUE = /\dpx/i;
134
+ const PX_VALUE = /(-?(?:\d+(?:\.\d+)?|\.\d+))px/gi;
135
+ function convertPx(pxStr, base, unit) {
136
+ const px = Number.parseFloat(pxStr);
137
+ const value = px / base;
138
+ const formatted = Number.parseFloat(value.toFixed(6)).toString();
139
+ return `${formatted}${unit}`;
140
+ }
141
+ function findReplacements(input, base, unit) {
142
+ const out = [];
143
+ for (const match of input.matchAll(ARBITRARY_BRACKET)) {
144
+ const full = match[0];
145
+ const inner = match[1];
146
+ if (!HAS_PX_VALUE.test(inner))
147
+ continue;
148
+ const fixedInner = inner.replace(PX_VALUE, (_m, num) => convertPx(num, base, unit));
149
+ out.push({
150
+ original: full,
151
+ fixed: `[${fixedInner}]`,
152
+ index: match.index,
153
+ length: full.length
154
+ });
155
+ }
156
+ return out;
157
+ }
158
+ function applyReplacements(input, replacements) {
159
+ let output = "";
160
+ let cursor = 0;
161
+ for (const r of replacements) {
162
+ output += input.slice(cursor, r.index) + r.fixed;
163
+ cursor = r.index + r.length;
164
+ }
165
+ output += input.slice(cursor);
166
+ return output;
167
+ }
168
+ const rule = {
169
+ meta: {
170
+ type: "problem",
171
+ docs: {
172
+ description: "Disallow `px` units inside Tailwind arbitrary value classes; prefer `rem` (or `em`).",
173
+ recommended: false
174
+ },
175
+ fixable: "code",
176
+ schema: [
177
+ {
178
+ type: "object",
179
+ additionalProperties: false,
180
+ properties: {
181
+ baseFontSize: { type: "number", minimum: 1 },
182
+ unit: { type: "string", enum: ["rem", "em"] }
183
+ }
184
+ }
185
+ ],
186
+ messages: {
187
+ preferRelativeUnit: "Tailwind class uses px; replace `{{original}}` with `{{fixed}}`."
188
+ }
189
+ },
190
+ create(context) {
191
+ const options = context.options[0] ?? {};
192
+ const base = options.baseFontSize ?? DEFAULT_BASE_FONT_SIZE;
193
+ const unit = options.unit ?? DEFAULT_UNIT;
194
+ const { sourceCode } = context;
195
+ function check(node) {
196
+ const raw = sourceCode.getText(node);
197
+ const replacements = findReplacements(raw, base, unit);
198
+ if (replacements.length === 0)
199
+ return;
200
+ const fixedRaw = applyReplacements(raw, replacements);
201
+ for (const r of replacements) {
202
+ context.report({
203
+ node,
204
+ messageId: "preferRelativeUnit",
205
+ data: { original: r.original, fixed: r.fixed },
206
+ fix: (fixer) => fixer.replaceText(node, fixedRaw)
207
+ });
208
+ }
209
+ }
210
+ const jsVisitor = {
211
+ Literal(node) {
212
+ if (typeof node.value !== "string")
213
+ return;
214
+ check(node);
215
+ },
216
+ TemplateElement(node) {
217
+ check(node);
218
+ }
219
+ };
220
+ const parserServices = context.sourceCode.parserServices;
221
+ if (parserServices.defineTemplateBodyVisitor) {
222
+ return parserServices.defineTemplateBodyVisitor(
223
+ { VLiteral: (node) => check(node) },
224
+ jsVisitor
225
+ );
226
+ }
227
+ return jsVisitor;
228
+ }
229
+ };
230
+
231
+ const rules = {
232
+ "tailwind-no-arbitrary-px": rule
233
+ };
234
+
235
+ const mazPlugin = {
236
+ meta: {
237
+ name: "maz"
238
+ },
239
+ rules
116
240
  };
117
241
 
242
+ const TAILWINDCSS_FILES = ["**/*.{js,jsx,cjs,mjs,ts,mts,cts,tsx,vue,html,svelte,astro,astrojs,css,scss}"];
243
+ const TAILWINDCSS_DEFAULT_FILES = TAILWINDCSS_FILES;
244
+ function resolveNoArbitraryPx(setting) {
245
+ if (setting === false)
246
+ return { severity: "off", options: { baseFontSize: 16, unit: "rem" } };
247
+ if (setting === void 0 || setting === true) {
248
+ return { severity: "error", options: { baseFontSize: 16, unit: "rem" } };
249
+ }
250
+ return {
251
+ severity: setting.severity ?? "error",
252
+ options: {
253
+ baseFontSize: setting.baseFontSize ?? 16,
254
+ unit: setting.unit ?? "rem"
255
+ }
256
+ };
257
+ }
258
+ function tailwindcssConfigs(preset, settings) {
259
+ const presetConfig = betterTailwindcss.configs[preset];
260
+ const tailwindSettings = {};
261
+ if (settings.entryPoint)
262
+ tailwindSettings.entryPoint = settings.entryPoint;
263
+ if (settings.tailwindConfig)
264
+ tailwindSettings.tailwindConfig = settings.tailwindConfig;
265
+ if (settings.detectComponentClasses !== void 0)
266
+ tailwindSettings.detectComponentClasses = settings.detectComponentClasses;
267
+ if (settings.cwd)
268
+ tailwindSettings.cwd = settings.cwd;
269
+ if (settings.tsconfig)
270
+ tailwindSettings.tsconfig = settings.tsconfig;
271
+ const noArbitraryPx = resolveNoArbitraryPx(settings.noArbitraryPx);
272
+ return [{
273
+ ...presetConfig,
274
+ files: TAILWINDCSS_FILES,
275
+ plugins: {
276
+ ...presetConfig.plugins,
277
+ maz: mazPlugin
278
+ },
279
+ settings: {
280
+ "better-tailwindcss": tailwindSettings
281
+ },
282
+ rules: {
283
+ ...presetConfig.rules,
284
+ "better-tailwindcss/no-unknown-classes": "off",
285
+ "better-tailwindcss/enforce-consistent-line-wrapping": "off",
286
+ "maz/tailwind-no-arbitrary-px": [noArbitraryPx.severity, noArbitraryPx.options]
287
+ }
288
+ }];
289
+ }
290
+
118
291
  const testRules = {
119
292
  "max-nested-callbacks": "off"
120
293
  };
@@ -122,7 +295,11 @@ const testRules = {
122
295
  const vueRules = {
123
296
  "vue/custom-event-name-casing": ["error", "kebab-case"]
124
297
  };
298
+ const vueSfcOnlyRules = {
299
+ "no-useless-assignment": "off"
300
+ };
125
301
 
302
+ const TAG = "[@maz-ui/eslint-config]";
126
303
  const defaultOptions = {
127
304
  formatters: true,
128
305
  typescript: true,
@@ -139,39 +316,93 @@ function getPackageJson() {
139
316
  return void 0;
140
317
  }
141
318
  }
142
- function getEslintMajorVersion() {
143
- try {
144
- const _require = createRequire(import.meta.url);
145
- const eslintPkg = _require("eslint/package.json");
146
- return Number(eslintPkg.version.split(".")[0]);
147
- } catch {
148
- return 0;
149
- }
319
+ function hasDependency(pkg, ...names) {
320
+ if (!pkg)
321
+ return false;
322
+ const buckets = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
323
+ return names.some((name) => buckets.some((bucket) => bucket && name in bucket));
150
324
  }
151
- function tryLoadTailwindPlugin() {
152
- try {
153
- const _require = createRequire(import.meta.url);
154
- const mod = _require("eslint-plugin-tailwindcss");
155
- return mod?.default ?? mod;
156
- } catch {
157
- return null;
158
- }
325
+ function resolveVue(option) {
326
+ if (option === true)
327
+ return { value: true, source: "explicit" };
328
+ if (option === false)
329
+ return { value: false, source: "explicit" };
330
+ return { value: hasDependency(getPackageJson(), "vue", "nuxt"), source: "auto-detected" };
331
+ }
332
+ function resolveTailwindcss(option) {
333
+ if (option === void 0)
334
+ return { value: false, source: "default" };
335
+ if (option === false)
336
+ return { value: false, source: "explicit" };
337
+ if (option === true)
338
+ return { value: { preset: "recommended", settings: {} }, source: "explicit" };
339
+ if (typeof option === "string")
340
+ return { value: { preset: option, settings: {} }, source: "explicit" };
341
+ return { value: { preset: option.preset ?? "recommended", settings: option }, source: "explicit" };
159
342
  }
160
- function isVueOrNuxtProject() {
161
- const packageJson = getPackageJson();
162
- return packageJson?.dependencies?.vue || packageJson?.devDependencies?.vue || packageJson?.peerDependencies?.vue || packageJson?.dependencies?.nuxt || packageJson?.devDependencies?.nuxt || packageJson?.peerDependencies?.nuxt;
343
+ function resolveOptions(opts, env) {
344
+ const vue = resolveVue(opts.vue);
345
+ const tailwind = resolveTailwindcss(opts.tailwindcss);
346
+ return {
347
+ typescript: opts.typescript !== false,
348
+ formatters: opts.formatters !== false,
349
+ unicorn: opts.unicorn !== false,
350
+ sonarjs: opts.sonarjs !== false,
351
+ vue: vue.value,
352
+ vueSource: vue.source,
353
+ vueAccessibility: opts.vueAccessibility === true,
354
+ tailwindcss: tailwind.value,
355
+ tailwindcssSource: tailwind.source,
356
+ env
357
+ };
358
+ }
359
+ function formatSourceTag(source) {
360
+ if (source === "auto-detected")
361
+ return " (auto-detected)";
362
+ if (source === "default")
363
+ return " (default)";
364
+ return "";
365
+ }
366
+ function formatResolutionBox(r) {
367
+ const tailwindLabel = r.tailwindcss === false ? "off" : r.tailwindcss.preset;
368
+ const rows = [
369
+ ["typescript", String(r.typescript)],
370
+ ["vue", `${r.vue}${formatSourceTag(r.vueSource)}`],
371
+ ["vueAccessibility", String(r.vueAccessibility)],
372
+ ["sonarjs", String(r.sonarjs)],
373
+ ["tailwindcss", `${tailwindLabel}${formatSourceTag(r.tailwindcssSource)}`],
374
+ ["formatters", String(r.formatters)],
375
+ ["unicorn", String(r.unicorn)],
376
+ ["env", r.env]
377
+ ];
378
+ const labelWidth = Math.max(...rows.map(([k]) => k.length));
379
+ return rows.map(([k, v]) => `${k.padEnd(labelWidth)} ${v}`).join("\n");
163
380
  }
164
381
  function defineConfig(options = {}, ...userConfigs) {
382
+ const log = createLogger();
383
+ const defaultLogLevel = process.stdout.isTTY ? "default" : "silent";
384
+ log.setLevel(options.logLevel ?? defaultLogLevel);
165
385
  const opts = { ...defaultOptions, ...options, ignores: [...GLOBAL_IGNORES, ...options.ignores || []] };
166
386
  const env = opts.env || process.env.NODE_ENV || "production";
387
+ const resolved = resolveOptions(opts, env);
388
+ log.box({
389
+ title: "@maz-ui/eslint-config",
390
+ message: formatResolutionBox(resolved),
391
+ style: { borderColor: "cyan", padding: 1 }
392
+ });
167
393
  const additionalConfigs = [];
168
- if (opts.vue || isVueOrNuxtProject()) {
394
+ if (resolved.vue) {
169
395
  additionalConfigs.push({
170
396
  files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
171
397
  rules: vueRules
172
398
  });
399
+ additionalConfigs.push({
400
+ files: ["**/*.vue"],
401
+ rules: vueSfcOnlyRules
402
+ });
403
+ log.debug(`${TAG} Vue: applied vueRules to JS/TS/Vue files + SFC-only overrides to *.vue`);
173
404
  }
174
- if (opts.sonarjs) {
405
+ if (resolved.sonarjs) {
175
406
  const sonarjsFiles = ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"];
176
407
  additionalConfigs.push({
177
408
  ...configs.recommended,
@@ -185,8 +416,9 @@ function defineConfig(options = {}, ...userConfigs) {
185
416
  files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
186
417
  rules: sonarjsTestRules
187
418
  });
419
+ log.debug(`${TAG} SonarJS: applied recommended preset + ${Object.keys(sonarjsRules).length} extra rules + test-file relaxations`);
188
420
  }
189
- if (opts.vueAccessibility) {
421
+ if (resolved.vueAccessibility) {
190
422
  const vueA11yConfigs = vueA11y.configs["flat/recommended"];
191
423
  const configsArray = Array.isArray(vueA11yConfigs) ? vueA11yConfigs : [vueA11yConfigs];
192
424
  const fixedConfigs = configsArray.map((config) => {
@@ -207,39 +439,35 @@ function defineConfig(options = {}, ...userConfigs) {
207
439
  return config;
208
440
  });
209
441
  additionalConfigs.push(...fixedConfigs);
442
+ log.debug(`${TAG} Vue a11y: applied ${fixedConfigs.length} flat-config block(s) from eslint-plugin-vuejs-accessibility`);
210
443
  }
211
- if (opts.tailwindcss) {
212
- const eslintMajor = getEslintMajorVersion();
213
- if (eslintMajor >= 10) {
214
- console.warn("[maz-eslint-config] eslint-plugin-tailwindcss is not compatible with ESLint 10+ (uses removed context.getSourceCode API). Tailwind CSS rules are disabled.");
215
- } else {
216
- const tailwindPlugin = tryLoadTailwindPlugin();
217
- if (!tailwindPlugin) {
218
- console.warn("[maz-eslint-config] opts.tailwindcss is true but eslint-plugin-tailwindcss is not installed. Add it to your devDependencies to enable the Tailwind CSS lint rules.");
219
- } else {
220
- const tailwindConfigs = tailwindPlugin.configs?.["flat/recommended"];
221
- if (Array.isArray(tailwindConfigs)) {
222
- additionalConfigs.push(...tailwindConfigs);
223
- }
224
- additionalConfigs.push({ rules: tailwindcssRules });
225
- }
226
- }
444
+ if (resolved.tailwindcss) {
445
+ const blocks = tailwindcssConfigs(resolved.tailwindcss.preset, resolved.tailwindcss.settings);
446
+ additionalConfigs.push(...blocks);
447
+ const settingKeys = Object.keys(resolved.tailwindcss.settings).filter((k) => k !== "preset");
448
+ const settingsHint = settingKeys.length > 0 ? ` with settings (${settingKeys.join(", ")})` : "";
449
+ log.debug(`${TAG} Tailwind: loaded "${resolved.tailwindcss.preset}" preset${settingsHint}`);
227
450
  }
228
451
  additionalConfigs.push({
229
452
  files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
230
453
  rules: testRules
231
454
  });
455
+ log.debug(`${TAG} Tests: relaxed rules applied to *.spec.{ts,js} / *.test.{ts,js}`);
456
+ if (options.rules && Object.keys(options.rules).length > 0)
457
+ log.debug(`${TAG} User: merged ${Object.keys(options.rules).length} rule override(s)`);
458
+ if (userConfigs.length > 0)
459
+ log.debug(`${TAG} User: appended ${userConfigs.length} flat-config argument(s)`);
460
+ log.verbose(`${TAG} Final config block count: ${additionalConfigs.length + userConfigs.length + 1} (additional: ${additionalConfigs.length}, user: ${userConfigs.length}, +markdown)`);
461
+ log.verbose(`${TAG} Ignore globs: ${opts.ignores.length}`);
462
+ const userRulesBlock = opts.rules && Object.keys(opts.rules).length > 0 ? { rules: opts.rules } : void 0;
232
463
  return antfu({
233
464
  formatters: opts.formatters,
234
465
  ...opts,
235
- rules: {
236
- ...baseRules(env === "production"),
237
- ...opts.rules
238
- },
466
+ rules: baseRules(env === "production"),
239
467
  ignores: (() => {
240
468
  return opts.ignores;
241
469
  })
242
- }, ...additionalConfigs, ...userConfigs, markdown);
470
+ }, ...additionalConfigs, ...userConfigs, ...userRulesBlock ? [userRulesBlock] : [], markdown);
243
471
  }
244
472
 
245
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
473
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, mazPlugin, rules as mazRules, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maz-ui/eslint-config",
3
3
  "type": "module",
4
- "version": "5.0.0-beta.0",
4
+ "version": "5.0.0-beta.11",
5
5
  "description": "ESLint configuration for JavaScript/TypeScript projects",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",
@@ -25,7 +25,8 @@
25
25
  "exports": {
26
26
  ".": {
27
27
  "types": "./dist/index.d.ts",
28
- "import": "./dist/index.mjs"
28
+ "import": "./dist/index.mjs",
29
+ "default": "./dist/index.mjs"
29
30
  }
30
31
  },
31
32
  "main": "./dist/index.mjs",
@@ -39,24 +40,18 @@
39
40
  "node": ">=20.19.0"
40
41
  },
41
42
  "peerDependencies": {
42
- "eslint": ">=9.0.0 <11.0.0",
43
- "eslint-plugin-tailwindcss": "^3.18.0"
44
- },
45
- "peerDependenciesMeta": {
46
- "eslint-plugin-tailwindcss": {
47
- "optional": true
48
- }
43
+ "eslint": ">=9.0.0 <11.0.0"
49
44
  },
50
45
  "dependencies": {
51
46
  "@antfu/eslint-config": "^8.2.0",
47
+ "consola": "^3.4.2",
48
+ "eslint-plugin-better-tailwindcss": "^4.5.0",
52
49
  "eslint-plugin-format": "^2.0.1",
53
50
  "eslint-plugin-sonarjs": "^4.0.3",
54
51
  "eslint-plugin-vuejs-accessibility": "^2.5.0"
55
52
  },
56
53
  "devDependencies": {
57
- "@types/eslint-plugin-tailwindcss": "^3.17.0",
58
54
  "eslint": "^10.3.0",
59
- "eslint-plugin-tailwindcss": "^3.18.3",
60
55
  "unbuild": "^3.6.1"
61
56
  },
62
57
  "lint-staged": {
@@ -70,6 +65,9 @@
70
65
  "dev": "unbuild --stub",
71
66
  "typecheck": "tsc --noEmit --skipLibCheck",
72
67
  "lint": "cross-env NODE_ENV=production eslint .",
73
- "lint:fix": "pnpm lint --fix"
68
+ "lint:fix": "pnpm lint --fix",
69
+ "test:unit": "vitest run",
70
+ "test:unit:coverage": "vitest run --coverage",
71
+ "test:unit:watch": "vitest watch"
74
72
  }
75
73
  }