@maz-ui/eslint-config 5.0.0-canary.ef64a02.0 → 5.0.0
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 +156 -90
- package/dist/index.d.mts +148 -18
- package/dist/index.d.ts +148 -18
- package/dist/index.mjs +296 -57
- package/package.json +18 -19
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**
|
|
8
|
-
- 🛡️ **
|
|
9
|
-
- 🎨 **Tailwind CSS** -
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
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
|
-
|
|
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
|
-
//
|
|
37
|
-
env: 'production', // 'development' | 'production'
|
|
40
|
+
env: 'production', // 'development' | 'production' — affects no-console / no-debugger severity
|
|
38
41
|
|
|
39
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
52
|
-
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
// 'silent' | 'default' | 'debug' | 'verbose' — see "Logging" below.
|
|
61
|
+
logLevel: 'default',
|
|
53
62
|
})
|
|
54
63
|
```
|
|
55
64
|
|
|
56
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
{
|
|
79
|
-
rules: {
|
|
80
|
-
...baseRules,
|
|
81
|
-
...sonarjsRules,
|
|
82
|
-
// Your custom rules
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
]
|
|
86
|
-
```
|
|
78
|
+
## Tailwind support
|
|
87
79
|
|
|
88
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
+
## Custom rules
|
|
108
110
|
|
|
109
|
-
|
|
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
|
-
###
|
|
113
|
+
### `maz/tailwind-no-arbitrary-px`
|
|
114
114
|
|
|
115
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
+
**Standard ESLint override** _(idiomatic, max control)_
|
|
126
123
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
135
|
+
**Ergonomic shortcut** _(set the defaults via `tailwindcss.noArbitraryPx`)_
|
|
134
136
|
|
|
135
137
|
```js
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
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
|
-
'
|
|
149
|
-
}
|
|
150
|
-
}
|
|
180
|
+
'maz/tailwind-no-arbitrary-px': ['error', { baseFontSize: 16 }],
|
|
181
|
+
},
|
|
182
|
+
}]
|
|
151
183
|
```
|
|
152
184
|
|
|
153
|
-
|
|
185
|
+
## Advanced
|
|
186
|
+
|
|
187
|
+
### Compose your own config
|
|
154
188
|
|
|
155
189
|
```js
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
'
|
|
161
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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,
|
|
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
|
-
*
|
|
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
|
|
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
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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,
|
|
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 {
|
|
@@ -29,8 +30,6 @@ function baseRules(isProduction) {
|
|
|
29
30
|
"no-unassigned-vars": "error",
|
|
30
31
|
"no-useless-assignment": "error",
|
|
31
32
|
"preserve-caught-error": "error",
|
|
32
|
-
// Unicorn
|
|
33
|
-
"unicorn/prefer-global-this": "error",
|
|
34
33
|
// Test
|
|
35
34
|
"test/prefer-lowercase-title": "off",
|
|
36
35
|
// e18e
|
|
@@ -71,6 +70,29 @@ const GLOBAL_IGNORES = [
|
|
|
71
70
|
"**/components.d.ts"
|
|
72
71
|
];
|
|
73
72
|
|
|
73
|
+
const LEVELS = {
|
|
74
|
+
silent: Number.NEGATIVE_INFINITY,
|
|
75
|
+
error: 0,
|
|
76
|
+
warning: 1,
|
|
77
|
+
normal: 2,
|
|
78
|
+
default: 3,
|
|
79
|
+
debug: 4,
|
|
80
|
+
trace: 5,
|
|
81
|
+
verbose: Number.POSITIVE_INFINITY
|
|
82
|
+
};
|
|
83
|
+
function createLogger() {
|
|
84
|
+
const consola = createConsola();
|
|
85
|
+
return {
|
|
86
|
+
setLevel: (level) => {
|
|
87
|
+
consola.level = LEVELS[level];
|
|
88
|
+
},
|
|
89
|
+
info: (msg) => consola.info(msg),
|
|
90
|
+
debug: (msg) => consola.debug(msg),
|
|
91
|
+
verbose: (msg) => consola.verbose(msg),
|
|
92
|
+
box: (opts) => consola.box(opts)
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
74
96
|
const markdown = {
|
|
75
97
|
files: ["**/*.md/**"],
|
|
76
98
|
rules: {
|
|
@@ -103,26 +125,188 @@ const sonarjsTestRules = {
|
|
|
103
125
|
"sonarjs/no-duplicate-string": "off"
|
|
104
126
|
};
|
|
105
127
|
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
128
|
+
const DEFAULT_BASE_FONT_SIZE = 16;
|
|
129
|
+
const DEFAULT_UNIT = "rem";
|
|
130
|
+
const ARBITRARY_BRACKET = /\[(?=([^\s\]]+))\1\]/g;
|
|
131
|
+
const HAS_PX_VALUE = /\dpx/i;
|
|
132
|
+
const PX_VALUE = /(-?(?=(\d+(?:\.\d+)?|\.\d+))\2)px/gi;
|
|
133
|
+
function convertPx(pxStr, base, unit) {
|
|
134
|
+
const px = Number.parseFloat(pxStr);
|
|
135
|
+
const value = px / base;
|
|
136
|
+
const formatted = Number.parseFloat(value.toFixed(6)).toString();
|
|
137
|
+
return `${formatted}${unit}`;
|
|
138
|
+
}
|
|
139
|
+
function findReplacements(input, base, unit) {
|
|
140
|
+
const out = [];
|
|
141
|
+
for (const match of input.matchAll(ARBITRARY_BRACKET)) {
|
|
142
|
+
const full = match[0];
|
|
143
|
+
const inner = match[1];
|
|
144
|
+
if (!HAS_PX_VALUE.test(inner))
|
|
145
|
+
continue;
|
|
146
|
+
const fixedInner = inner.replace(PX_VALUE, (_m, num) => convertPx(num, base, unit));
|
|
147
|
+
out.push({
|
|
148
|
+
original: full,
|
|
149
|
+
fixed: `[${fixedInner}]`,
|
|
150
|
+
index: match.index,
|
|
151
|
+
length: full.length
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
function applyReplacements(input, replacements) {
|
|
157
|
+
let output = "";
|
|
158
|
+
let cursor = 0;
|
|
159
|
+
for (const r of replacements) {
|
|
160
|
+
output += input.slice(cursor, r.index) + r.fixed;
|
|
161
|
+
cursor = r.index + r.length;
|
|
162
|
+
}
|
|
163
|
+
output += input.slice(cursor);
|
|
164
|
+
return output;
|
|
165
|
+
}
|
|
166
|
+
const rule = {
|
|
167
|
+
meta: {
|
|
168
|
+
type: "problem",
|
|
169
|
+
docs: {
|
|
170
|
+
description: "Disallow `px` units inside Tailwind arbitrary value classes; prefer `rem` (or `em`).",
|
|
171
|
+
recommended: false
|
|
172
|
+
},
|
|
173
|
+
fixable: "code",
|
|
174
|
+
schema: [
|
|
175
|
+
{
|
|
176
|
+
type: "object",
|
|
177
|
+
additionalProperties: false,
|
|
178
|
+
properties: {
|
|
179
|
+
baseFontSize: { type: "number", minimum: 1 },
|
|
180
|
+
unit: { type: "string", enum: ["rem", "em"] }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
],
|
|
184
|
+
messages: {
|
|
185
|
+
preferRelativeUnit: "Tailwind class uses px; replace `{{original}}` with `{{fixed}}`."
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
create(context) {
|
|
189
|
+
const options = context.options[0] ?? {};
|
|
190
|
+
const base = options.baseFontSize ?? DEFAULT_BASE_FONT_SIZE;
|
|
191
|
+
const unit = options.unit ?? DEFAULT_UNIT;
|
|
192
|
+
const { sourceCode } = context;
|
|
193
|
+
function check(node) {
|
|
194
|
+
const raw = sourceCode.getText(node);
|
|
195
|
+
const replacements = findReplacements(raw, base, unit);
|
|
196
|
+
if (replacements.length === 0)
|
|
197
|
+
return;
|
|
198
|
+
const fixedRaw = applyReplacements(raw, replacements);
|
|
199
|
+
for (const r of replacements) {
|
|
200
|
+
context.report({
|
|
201
|
+
node,
|
|
202
|
+
messageId: "preferRelativeUnit",
|
|
203
|
+
data: { original: r.original, fixed: r.fixed },
|
|
204
|
+
fix: (fixer) => fixer.replaceText(node, fixedRaw)
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
const jsVisitor = {
|
|
209
|
+
Literal(node) {
|
|
210
|
+
if (typeof node.value !== "string")
|
|
211
|
+
return;
|
|
212
|
+
check(node);
|
|
213
|
+
},
|
|
214
|
+
TemplateElement(node) {
|
|
215
|
+
check(node);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
const parserServices = context.sourceCode.parserServices;
|
|
219
|
+
if (parserServices.defineTemplateBodyVisitor) {
|
|
220
|
+
return parserServices.defineTemplateBodyVisitor(
|
|
221
|
+
{ VLiteral: (node) => check(node) },
|
|
222
|
+
jsVisitor
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return jsVisitor;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const rules = {
|
|
230
|
+
"tailwind-no-arbitrary-px": rule
|
|
116
231
|
};
|
|
117
232
|
|
|
233
|
+
const mazPlugin = {
|
|
234
|
+
meta: {
|
|
235
|
+
name: "maz"
|
|
236
|
+
},
|
|
237
|
+
rules
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const TAILWINDCSS_FILES = ["**/*.{js,jsx,cjs,mjs,ts,mts,cts,tsx,vue,html,svelte,astro,astrojs,css,scss}"];
|
|
241
|
+
const TAILWINDCSS_DEFAULT_FILES = TAILWINDCSS_FILES;
|
|
242
|
+
function resolveNoArbitraryPx(setting) {
|
|
243
|
+
if (setting === false)
|
|
244
|
+
return { severity: "off", options: { baseFontSize: 16, unit: "rem" } };
|
|
245
|
+
if (setting === void 0 || setting === true) {
|
|
246
|
+
return { severity: "error", options: { baseFontSize: 16, unit: "rem" } };
|
|
247
|
+
}
|
|
248
|
+
return {
|
|
249
|
+
severity: setting.severity ?? "error",
|
|
250
|
+
options: {
|
|
251
|
+
baseFontSize: setting.baseFontSize ?? 16,
|
|
252
|
+
unit: setting.unit ?? "rem"
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function tailwindcssConfigs(preset, settings) {
|
|
257
|
+
const presetConfig = betterTailwindcss.configs[preset];
|
|
258
|
+
const tailwindSettings = {};
|
|
259
|
+
if (settings.entryPoint)
|
|
260
|
+
tailwindSettings.entryPoint = settings.entryPoint;
|
|
261
|
+
if (settings.tailwindConfig)
|
|
262
|
+
tailwindSettings.tailwindConfig = settings.tailwindConfig;
|
|
263
|
+
if (settings.detectComponentClasses !== void 0)
|
|
264
|
+
tailwindSettings.detectComponentClasses = settings.detectComponentClasses;
|
|
265
|
+
if (settings.cwd)
|
|
266
|
+
tailwindSettings.cwd = settings.cwd;
|
|
267
|
+
if (settings.tsconfig)
|
|
268
|
+
tailwindSettings.tsconfig = settings.tsconfig;
|
|
269
|
+
const noArbitraryPx = resolveNoArbitraryPx(settings.noArbitraryPx);
|
|
270
|
+
return [{
|
|
271
|
+
...presetConfig,
|
|
272
|
+
files: TAILWINDCSS_FILES,
|
|
273
|
+
plugins: {
|
|
274
|
+
...presetConfig.plugins,
|
|
275
|
+
maz: mazPlugin
|
|
276
|
+
},
|
|
277
|
+
settings: {
|
|
278
|
+
"better-tailwindcss": tailwindSettings
|
|
279
|
+
},
|
|
280
|
+
rules: {
|
|
281
|
+
...presetConfig.rules,
|
|
282
|
+
"better-tailwindcss/no-unknown-classes": "off",
|
|
283
|
+
"better-tailwindcss/enforce-consistent-line-wrapping": "off",
|
|
284
|
+
"better-tailwindcss/no-concatenated-classes": "off",
|
|
285
|
+
"maz/tailwind-no-arbitrary-px": [noArbitraryPx.severity, noArbitraryPx.options]
|
|
286
|
+
}
|
|
287
|
+
}];
|
|
288
|
+
}
|
|
289
|
+
|
|
118
290
|
const testRules = {
|
|
119
291
|
"max-nested-callbacks": "off"
|
|
120
292
|
};
|
|
121
293
|
|
|
294
|
+
const unicorn = {
|
|
295
|
+
files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
|
|
296
|
+
rules: {
|
|
297
|
+
"unicorn/prefer-global-this": "error"
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
|
|
122
301
|
const vueRules = {
|
|
123
302
|
"vue/custom-event-name-casing": ["error", "kebab-case"]
|
|
124
303
|
};
|
|
304
|
+
const vueSfcOnlyRules = {
|
|
305
|
+
"no-useless-assignment": "off"
|
|
306
|
+
};
|
|
125
307
|
|
|
308
|
+
process.env.SYNCKIT_TIMEOUT ??= "120000";
|
|
309
|
+
const TAG = "[@maz-ui/eslint-config]";
|
|
126
310
|
const defaultOptions = {
|
|
127
311
|
formatters: true,
|
|
128
312
|
typescript: true,
|
|
@@ -139,39 +323,93 @@ function getPackageJson() {
|
|
|
139
323
|
return void 0;
|
|
140
324
|
}
|
|
141
325
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
} catch {
|
|
148
|
-
return 0;
|
|
149
|
-
}
|
|
326
|
+
function hasDependency(pkg, ...names) {
|
|
327
|
+
if (!pkg)
|
|
328
|
+
return false;
|
|
329
|
+
const buckets = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
|
|
330
|
+
return names.some((name) => buckets.some((bucket) => bucket && name in bucket));
|
|
150
331
|
}
|
|
151
|
-
function
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
332
|
+
function resolveVue(option) {
|
|
333
|
+
if (option === true)
|
|
334
|
+
return { value: true, source: "explicit" };
|
|
335
|
+
if (option === false)
|
|
336
|
+
return { value: false, source: "explicit" };
|
|
337
|
+
return { value: hasDependency(getPackageJson(), "vue", "nuxt"), source: "auto-detected" };
|
|
338
|
+
}
|
|
339
|
+
function resolveTailwindcss(option) {
|
|
340
|
+
if (option === void 0)
|
|
341
|
+
return { value: false, source: "default" };
|
|
342
|
+
if (option === false)
|
|
343
|
+
return { value: false, source: "explicit" };
|
|
344
|
+
if (option === true)
|
|
345
|
+
return { value: { preset: "recommended", settings: {} }, source: "explicit" };
|
|
346
|
+
if (typeof option === "string")
|
|
347
|
+
return { value: { preset: option, settings: {} }, source: "explicit" };
|
|
348
|
+
return { value: { preset: option.preset ?? "recommended", settings: option }, source: "explicit" };
|
|
349
|
+
}
|
|
350
|
+
function resolveOptions(opts, env) {
|
|
351
|
+
const vue = resolveVue(opts.vue);
|
|
352
|
+
const tailwind = resolveTailwindcss(opts.tailwindcss);
|
|
353
|
+
return {
|
|
354
|
+
typescript: opts.typescript !== false,
|
|
355
|
+
formatters: opts.formatters !== false,
|
|
356
|
+
unicorn: opts.unicorn !== false,
|
|
357
|
+
sonarjs: opts.sonarjs !== false,
|
|
358
|
+
vue: vue.value,
|
|
359
|
+
vueSource: vue.source,
|
|
360
|
+
vueAccessibility: opts.vueAccessibility === true,
|
|
361
|
+
tailwindcss: tailwind.value,
|
|
362
|
+
tailwindcssSource: tailwind.source,
|
|
363
|
+
env
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function formatSourceTag(source) {
|
|
367
|
+
if (source === "auto-detected")
|
|
368
|
+
return " (auto-detected)";
|
|
369
|
+
if (source === "default")
|
|
370
|
+
return " (default)";
|
|
371
|
+
return "";
|
|
159
372
|
}
|
|
160
|
-
function
|
|
161
|
-
const
|
|
162
|
-
|
|
373
|
+
function formatResolutionBox(r) {
|
|
374
|
+
const tailwindLabel = r.tailwindcss === false ? "off" : r.tailwindcss.preset;
|
|
375
|
+
const rows = [
|
|
376
|
+
["typescript", String(r.typescript)],
|
|
377
|
+
["vue", `${r.vue}${formatSourceTag(r.vueSource)}`],
|
|
378
|
+
["vueAccessibility", String(r.vueAccessibility)],
|
|
379
|
+
["sonarjs", String(r.sonarjs)],
|
|
380
|
+
["tailwindcss", `${tailwindLabel}${formatSourceTag(r.tailwindcssSource)}`],
|
|
381
|
+
["formatters", String(r.formatters)],
|
|
382
|
+
["unicorn", String(r.unicorn)],
|
|
383
|
+
["env", r.env]
|
|
384
|
+
];
|
|
385
|
+
const labelWidth = Math.max(...rows.map(([k]) => k.length));
|
|
386
|
+
return rows.map(([k, v]) => `${k.padEnd(labelWidth)} ${v}`).join("\n");
|
|
163
387
|
}
|
|
164
388
|
function defineConfig(options = {}, ...userConfigs) {
|
|
389
|
+
const log = createLogger();
|
|
390
|
+
const defaultLogLevel = process.stdout.isTTY ? "default" : "silent";
|
|
391
|
+
log.setLevel(options.logLevel ?? defaultLogLevel);
|
|
165
392
|
const opts = { ...defaultOptions, ...options, ignores: [...GLOBAL_IGNORES, ...options.ignores || []] };
|
|
166
393
|
const env = opts.env || process.env.NODE_ENV || "production";
|
|
394
|
+
const resolved = resolveOptions(opts, env);
|
|
395
|
+
log.box({
|
|
396
|
+
title: "@maz-ui/eslint-config",
|
|
397
|
+
message: formatResolutionBox(resolved),
|
|
398
|
+
style: { borderColor: "cyan", padding: 1 }
|
|
399
|
+
});
|
|
167
400
|
const additionalConfigs = [];
|
|
168
|
-
if (
|
|
401
|
+
if (resolved.vue) {
|
|
169
402
|
additionalConfigs.push({
|
|
170
403
|
files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
|
|
171
404
|
rules: vueRules
|
|
172
405
|
});
|
|
406
|
+
additionalConfigs.push({
|
|
407
|
+
files: ["**/*.vue"],
|
|
408
|
+
rules: vueSfcOnlyRules
|
|
409
|
+
});
|
|
410
|
+
log.debug(`${TAG} Vue: applied vueRules to JS/TS/Vue files + SFC-only overrides to *.vue`);
|
|
173
411
|
}
|
|
174
|
-
if (
|
|
412
|
+
if (resolved.sonarjs) {
|
|
175
413
|
const sonarjsFiles = ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"];
|
|
176
414
|
additionalConfigs.push({
|
|
177
415
|
...configs.recommended,
|
|
@@ -185,8 +423,13 @@ function defineConfig(options = {}, ...userConfigs) {
|
|
|
185
423
|
files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
|
|
186
424
|
rules: sonarjsTestRules
|
|
187
425
|
});
|
|
426
|
+
log.debug(`${TAG} SonarJS: applied recommended preset + ${Object.keys(sonarjsRules).length} extra rules + test-file relaxations`);
|
|
427
|
+
}
|
|
428
|
+
if (resolved.unicorn) {
|
|
429
|
+
additionalConfigs.push(unicorn);
|
|
430
|
+
log.debug(`${TAG} Unicorn: applied JS/TS-scoped rule overrides`);
|
|
188
431
|
}
|
|
189
|
-
if (
|
|
432
|
+
if (resolved.vueAccessibility) {
|
|
190
433
|
const vueA11yConfigs = vueA11y.configs["flat/recommended"];
|
|
191
434
|
const configsArray = Array.isArray(vueA11yConfigs) ? vueA11yConfigs : [vueA11yConfigs];
|
|
192
435
|
const fixedConfigs = configsArray.map((config) => {
|
|
@@ -207,39 +450,35 @@ function defineConfig(options = {}, ...userConfigs) {
|
|
|
207
450
|
return config;
|
|
208
451
|
});
|
|
209
452
|
additionalConfigs.push(...fixedConfigs);
|
|
453
|
+
log.debug(`${TAG} Vue a11y: applied ${fixedConfigs.length} flat-config block(s) from eslint-plugin-vuejs-accessibility`);
|
|
210
454
|
}
|
|
211
|
-
if (
|
|
212
|
-
const
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
|
|
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
|
-
}
|
|
455
|
+
if (resolved.tailwindcss) {
|
|
456
|
+
const blocks = tailwindcssConfigs(resolved.tailwindcss.preset, resolved.tailwindcss.settings);
|
|
457
|
+
additionalConfigs.push(...blocks);
|
|
458
|
+
const settingKeys = Object.keys(resolved.tailwindcss.settings).filter((k) => k !== "preset");
|
|
459
|
+
const settingsHint = settingKeys.length > 0 ? ` with settings (${settingKeys.join(", ")})` : "";
|
|
460
|
+
log.debug(`${TAG} Tailwind: loaded "${resolved.tailwindcss.preset}" preset${settingsHint}`);
|
|
227
461
|
}
|
|
228
462
|
additionalConfigs.push({
|
|
229
463
|
files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
|
|
230
464
|
rules: testRules
|
|
231
465
|
});
|
|
466
|
+
log.debug(`${TAG} Tests: relaxed rules applied to *.spec.{ts,js} / *.test.{ts,js}`);
|
|
467
|
+
if (options.rules && Object.keys(options.rules).length > 0)
|
|
468
|
+
log.debug(`${TAG} User: merged ${Object.keys(options.rules).length} rule override(s)`);
|
|
469
|
+
if (userConfigs.length > 0)
|
|
470
|
+
log.debug(`${TAG} User: appended ${userConfigs.length} flat-config argument(s)`);
|
|
471
|
+
log.verbose(`${TAG} Final config block count: ${additionalConfigs.length + userConfigs.length + 1} (additional: ${additionalConfigs.length}, user: ${userConfigs.length}, +markdown)`);
|
|
472
|
+
log.verbose(`${TAG} Ignore globs: ${opts.ignores.length}`);
|
|
473
|
+
const userRulesBlock = opts.rules && Object.keys(opts.rules).length > 0 ? { rules: opts.rules } : void 0;
|
|
232
474
|
return antfu({
|
|
233
475
|
formatters: opts.formatters,
|
|
234
476
|
...opts,
|
|
235
|
-
rules:
|
|
236
|
-
...baseRules(env === "production"),
|
|
237
|
-
...opts.rules
|
|
238
|
-
},
|
|
477
|
+
rules: baseRules(env === "production"),
|
|
239
478
|
ignores: (() => {
|
|
240
479
|
return opts.ignores;
|
|
241
480
|
})
|
|
242
|
-
}, ...additionalConfigs, ...userConfigs, markdown);
|
|
481
|
+
}, ...additionalConfigs, ...userConfigs, ...userRulesBlock ? [userRulesBlock] : [], markdown);
|
|
243
482
|
}
|
|
244
483
|
|
|
245
|
-
export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules,
|
|
484
|
+
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
|
|
4
|
+
"version": "5.0.0",
|
|
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",
|
|
@@ -36,28 +37,24 @@
|
|
|
36
37
|
"dist"
|
|
37
38
|
],
|
|
38
39
|
"engines": {
|
|
39
|
-
"node": ">=
|
|
40
|
+
"node": ">=22.12.0"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"eslint": "
|
|
43
|
-
"eslint-plugin-tailwindcss": "^3.18.0"
|
|
44
|
-
},
|
|
45
|
-
"peerDependenciesMeta": {
|
|
46
|
-
"eslint-plugin-tailwindcss": {
|
|
47
|
-
"optional": true
|
|
48
|
-
}
|
|
43
|
+
"eslint": "^9.10.0 || ^10.0.0"
|
|
49
44
|
},
|
|
50
45
|
"dependencies": {
|
|
51
|
-
"@antfu/eslint-config": "^
|
|
46
|
+
"@antfu/eslint-config": "^9.5.1",
|
|
47
|
+
"consola": "^3.4.2",
|
|
48
|
+
"eslint-plugin-better-tailwindcss": "^4.10.0",
|
|
52
49
|
"eslint-plugin-format": "^2.0.1",
|
|
53
|
-
"eslint-plugin-sonarjs": "^4.
|
|
54
|
-
"eslint-plugin-vuejs-accessibility": "^2.
|
|
50
|
+
"eslint-plugin-sonarjs": "^4.2.2",
|
|
51
|
+
"eslint-plugin-vuejs-accessibility": "^2.6.2"
|
|
55
52
|
},
|
|
56
53
|
"devDependencies": {
|
|
57
|
-
"@
|
|
58
|
-
"eslint": "^10.
|
|
59
|
-
"
|
|
60
|
-
"
|
|
54
|
+
"@typescript-eslint/rule-tester": "^8.72.0",
|
|
55
|
+
"eslint": "^10.13.0",
|
|
56
|
+
"unbuild": "^3.6.1",
|
|
57
|
+
"vue-eslint-parser": "^10.4.1"
|
|
61
58
|
},
|
|
62
59
|
"lint-staged": {
|
|
63
60
|
"*.{js,jsx,ts,tsx,mjs,mts,cjs,md,yml,json}": [
|
|
@@ -65,11 +62,13 @@
|
|
|
65
62
|
]
|
|
66
63
|
},
|
|
67
64
|
"scripts": {
|
|
68
|
-
"codegen": "nx build",
|
|
69
65
|
"build": "unbuild",
|
|
70
66
|
"dev": "unbuild --stub",
|
|
71
67
|
"typecheck": "tsc --noEmit --skipLibCheck",
|
|
72
68
|
"lint": "cross-env NODE_ENV=production eslint .",
|
|
73
|
-
"lint:fix": "pnpm lint --fix"
|
|
69
|
+
"lint:fix": "pnpm lint --fix",
|
|
70
|
+
"test:unit": "vitest run",
|
|
71
|
+
"test:unit:coverage": "vitest run --coverage",
|
|
72
|
+
"test:unit:watch": "vitest watch"
|
|
74
73
|
}
|
|
75
74
|
}
|