@maz-ui/eslint-config 4.9.1 → 5.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,15 +1,16 @@
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
+ - 🔍 **SonarJS** code quality / cognitive complexity
11
+ - **Vue accessibility** — opt-in `eslint-plugin-vuejs-accessibility`
12
+ - 📐 **Formatters** — Prettier-style formatting via `eslint-plugin-format`
13
+ - ⚙️ **Configurable** — pick presets, add per-file rules, custom ignore globs
13
14
 
14
15
  ## Installation
15
16
 
@@ -26,139 +27,119 @@ import { defineConfig } from '@maz-ui/eslint-config'
26
27
  export default defineConfig()
27
28
  ```
28
29
 
29
- ## Custom configuration
30
+ Vue / Nuxt are auto-detected from your `package.json`.
31
+
32
+ ## Configuration
30
33
 
31
34
  ```js
32
35
  // eslint.config.js
33
36
  import { defineConfig } from '@maz-ui/eslint-config'
34
37
 
35
38
  export default defineConfig({
36
- // Environment (affects console rules)
37
- env: 'production', // 'development' | 'production'
39
+ env: 'production', // 'development' | 'production' — affects no-console / no-debugger severity
38
40
 
39
- // Enable/disable plugins
41
+ // Antfu options (forwarded as-is)
40
42
  typescript: true,
41
- tailwindcss: true,
42
- sonarjs: true,
43
43
  formatters: true,
44
+ unicorn: true,
45
+
46
+ // Maz-UI options
47
+ sonarjs: true,
48
+ vueAccessibility: false,
49
+ tailwindcss: 'recommended', // see "Tailwind support" below
44
50
 
45
- // Files to ignore
51
+ // Extra ignore globs (merged with defaults)
46
52
  ignores: ['custom-dist/**'],
47
53
 
48
54
  // Custom rules
49
55
  rules: {
50
56
  'no-console': 'error',
51
- 'prefer-const': 'warn'
52
- }
57
+ },
58
+
59
+ // 'silent' | 'default' | 'debug' | 'verbose' — see "Logging" below.
60
+ logLevel: 'default',
53
61
  })
54
62
  ```
55
63
 
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
- ```
64
+ ## Logging
69
65
 
70
- ## Advanced usage
66
+ 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
67
 
72
- ### Selective rule imports
68
+ | Value | What you see |
69
+ | --------------------------- | -------------------------------------------------- |
70
+ | `'silent'` | Nothing. |
71
+ | `'default'` _(TTY default)_ | Titled box of resolved feature toggles. |
72
+ | `'debug'` | Above + each plugin / preset / overrides addition. |
73
+ | `'verbose'` | Above + final block count and ignore-glob count. |
73
74
 
74
- ```js
75
- import { baseRules, sonarjsRules, tailwindcssRules } from '@maz-ui/eslint-config'
75
+ 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
76
 
77
- export default [
78
- {
79
- rules: {
80
- ...baseRules,
81
- ...sonarjsRules,
82
- // Your custom rules
83
- }
84
- }
85
- ]
86
- ```
77
+ ## Tailwind support
87
78
 
88
- ### Example for Vue project
79
+ Powered by [`eslint-plugin-better-tailwindcss`](https://github.com/schoero/eslint-plugin-better-tailwindcss) compatible with ESLint 7→10 and Tailwind v3 + v4.
89
80
 
90
- ```js
91
- // eslint.config.js
92
- import { defineConfig } from '@maz-ui/eslint-config'
81
+ | Value | Behavior |
82
+ | ----------------------- | ----------------------------------------------------------------------- |
83
+ | `false` _(default)_ | Disabled. |
84
+ | `true` | Same as `'recommended'` with default settings. |
85
+ | `'recommended'` | Stylistic + correctness rules. |
86
+ | `'stylistic'` | Formatting rules only (class order, line wrapping, …). |
87
+ | `'correctness'` | Validation rules only (`no-unknown-classes`, `no-conflicting-classes`). |
88
+ | `MazTailwindcssOptions` | Pick a preset and pass plugin settings. |
93
89
 
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
- }
90
+ ```js
91
+ defineConfig({
92
+ tailwindcss: {
93
+ preset: 'recommended',
94
+ // Tailwind v4: path to the CSS that does `@import "tailwindcss" prefix(maz)`
95
+ entryPoint: 'src/main.css',
96
+ // Tailwind v3 instead:
97
+ // tailwindConfig: 'tailwind.config.ts',
98
+ detectComponentClasses: true,
99
+ },
102
100
  })
103
101
  ```
104
102
 
105
- ## Included rules
106
-
107
- ### Base (JavaScript/TypeScript)
108
-
109
- - Console management by environment
110
- - Code quality rules
111
- - Optimized TypeScript support
112
-
113
- ### SonarJS
114
-
115
- - Limited cognitive complexity
116
- - Duplicate code detection
117
- - Security best practices
103
+ > [!IMPORTANT]
104
+ > 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.
118
105
 
119
- ### Tailwind CSS
106
+ ## Advanced
120
107
 
121
- - Consistent class ordering
122
- - Contradictory class detection
123
- - Valid Tailwind syntax
124
-
125
- ## Compatibility
126
-
127
- - **ESLint** ^9.0.0
128
- - **Node.js** >=18.0.0
129
- - **TypeScript** ^5.0.0
130
-
131
- ## Usage examples
132
-
133
- ### Simple JavaScript project
108
+ ### Compose your own config
134
109
 
135
110
  ```js
136
- export default defineConfig({
137
- typescript: false,
138
- tailwindcss: false
139
- })
111
+ import {
112
+ baseRules,
113
+ sonarjsRules,
114
+ tailwindcssConfigs,
115
+ vueRules,
116
+ } from '@maz-ui/eslint-config'
117
+
118
+ export default [
119
+ {
120
+ rules: {
121
+ ...baseRules(true), // production = true
122
+ ...sonarjsRules,
123
+ ...vueRules,
124
+ },
125
+ },
126
+ ...tailwindcssConfigs('stylistic', { entryPoint: 'src/main.css' }),
127
+ ]
140
128
  ```
141
129
 
142
- ### Project with Tailwind
130
+ ### Vue project
143
131
 
144
132
  ```js
145
- export default defineConfig({
146
- tailwindcss: true,
133
+ defineConfig({
134
+ vue: true, // auto-detected if `vue` / `nuxt` is in deps
135
+ tailwindcss: 'recommended',
147
136
  rules: {
148
- 'tailwindcss/classnames-order': 'error'
149
- }
137
+ 'vue/custom-event-name-casing': ['error', 'kebab-case'],
138
+ },
150
139
  })
151
140
  ```
152
141
 
153
- ### Production project
142
+ ## Compatibility
154
143
 
155
- ```js
156
- export default defineConfig({
157
- env: 'production',
158
- sonarjs: true,
159
- rules: {
160
- 'no-console': 'error',
161
- 'no-debugger': 'error'
162
- }
163
- })
164
- ```
144
+ - **ESLint** `^9.0.0 || ^10.0.0`
145
+ - **Node.js** `>=20.19.0`
package/dist/index.d.mts CHANGED
@@ -1,13 +1,73 @@
1
1
  import { antfu, OptionsConfig, Rules, TypedFlatConfigItem } from '@antfu/eslint-config';
2
2
  import { Linter } from 'eslint';
3
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';
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
+ }
5
57
  interface MazESLintOptions extends OptionsConfig {
6
58
  /**
7
- * Enable Tailwind CSS support
59
+ * Tailwind CSS support — wires `eslint-plugin-better-tailwindcss`.
60
+ *
61
+ * - `false` *(default)*: disabled.
62
+ * - `true`: load the `'recommended'` preset with default settings.
63
+ * - `'recommended' | 'stylistic' | 'correctness'`: pick a preset, default
64
+ * settings.
65
+ * - `MazTailwindcssOptions`: pick a preset and pass settings (e.g.
66
+ * `entryPoint` so the plugin learns your v4 prefix).
67
+ *
8
68
  * @default false
9
69
  */
10
- tailwindcss?: boolean;
70
+ tailwindcss?: boolean | TailwindcssPreset | MazTailwindcssOptions;
11
71
  /**
12
72
  * Enable SonarJS rules for code quality
13
73
  * @default true
@@ -32,6 +92,19 @@ interface MazESLintOptions extends OptionsConfig {
32
92
  * Additional rules to merge
33
93
  */
34
94
  rules?: Partial<Rules>;
95
+ /**
96
+ * Verbosity of the logs emitted while resolving the config. Defaults to
97
+ * `'default'` so the resolved-configuration box is shown when the preset
98
+ * loads. Set to `'silent'` to hide it, or `'debug'` / `'verbose'` to dig
99
+ * deeper.
100
+ *
101
+ * - `'silent'`: nothing is printed.
102
+ * - `'default'` *(default)*: a titled box summarizing the resolved feature
103
+ * toggles.
104
+ * - `'debug'`: also logs each plugin / preset / overrides addition.
105
+ * - `'verbose'`: also logs the final shape (config block count, etc.).
106
+ */
107
+ logLevel?: LogLevel;
35
108
  }
36
109
  type MazESLintConfig = ReturnType<typeof antfu>;
37
110
 
@@ -68,15 +141,17 @@ declare const sonarjsTestRules: {
68
141
  };
69
142
 
70
143
  /**
71
- * Tailwind CSS ESLint rules configuration
144
+ * Files where Tailwind classes commonly live. Exposed for users who want to
145
+ * compose their own config.
72
146
  */
73
- declare const tailwindcssRules: {
74
- readonly 'tailwindcss/no-custom-classname': "off";
75
- readonly 'tailwindcss/classnames-order': "warn";
76
- readonly 'tailwindcss/no-contradicting-classname': "error";
77
- readonly 'tailwindcss/enforces-negative-arbitrary-values': "error";
78
- readonly 'tailwindcss/enforces-shorthand': "warn";
79
- };
147
+ declare const TAILWINDCSS_DEFAULT_FILES: string[];
148
+ /**
149
+ * Build the flat-config block(s) that wire up
150
+ * `eslint-plugin-better-tailwindcss`. The plugin already ships preset
151
+ * objects with `plugins` + `rules`; we spread them and add `files` plus
152
+ * `settings['better-tailwindcss']` so the user's options reach the plugin.
153
+ */
154
+ declare function tailwindcssConfigs(preset: TailwindcssPreset, settings: MazTailwindcssOptions): Linter.Config[];
80
155
 
81
156
  declare const vueRules: {
82
157
  'vue/custom-event-name-casing': ["error", "kebab-case"];
@@ -102,5 +177,5 @@ declare const vueRules: {
102
177
  */
103
178
  declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
104
179
 
105
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
106
- export type { MazESLintConfig, MazESLintOptions };
180
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules };
181
+ export type { MazESLintConfig, MazESLintOptions, MazTailwindcssOptions, TailwindcssPreset };
package/dist/index.d.ts CHANGED
@@ -1,13 +1,73 @@
1
1
  import { antfu, OptionsConfig, Rules, TypedFlatConfigItem } from '@antfu/eslint-config';
2
2
  import { Linter } from 'eslint';
3
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';
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
+ }
5
57
  interface MazESLintOptions extends OptionsConfig {
6
58
  /**
7
- * Enable Tailwind CSS support
59
+ * Tailwind CSS support — wires `eslint-plugin-better-tailwindcss`.
60
+ *
61
+ * - `false` *(default)*: disabled.
62
+ * - `true`: load the `'recommended'` preset with default settings.
63
+ * - `'recommended' | 'stylistic' | 'correctness'`: pick a preset, default
64
+ * settings.
65
+ * - `MazTailwindcssOptions`: pick a preset and pass settings (e.g.
66
+ * `entryPoint` so the plugin learns your v4 prefix).
67
+ *
8
68
  * @default false
9
69
  */
10
- tailwindcss?: boolean;
70
+ tailwindcss?: boolean | TailwindcssPreset | MazTailwindcssOptions;
11
71
  /**
12
72
  * Enable SonarJS rules for code quality
13
73
  * @default true
@@ -32,6 +92,19 @@ interface MazESLintOptions extends OptionsConfig {
32
92
  * Additional rules to merge
33
93
  */
34
94
  rules?: Partial<Rules>;
95
+ /**
96
+ * Verbosity of the logs emitted while resolving the config. Defaults to
97
+ * `'default'` so the resolved-configuration box is shown when the preset
98
+ * loads. Set to `'silent'` to hide it, or `'debug'` / `'verbose'` to dig
99
+ * deeper.
100
+ *
101
+ * - `'silent'`: nothing is printed.
102
+ * - `'default'` *(default)*: a titled box summarizing the resolved feature
103
+ * toggles.
104
+ * - `'debug'`: also logs each plugin / preset / overrides addition.
105
+ * - `'verbose'`: also logs the final shape (config block count, etc.).
106
+ */
107
+ logLevel?: LogLevel;
35
108
  }
36
109
  type MazESLintConfig = ReturnType<typeof antfu>;
37
110
 
@@ -68,15 +141,17 @@ declare const sonarjsTestRules: {
68
141
  };
69
142
 
70
143
  /**
71
- * Tailwind CSS ESLint rules configuration
144
+ * Files where Tailwind classes commonly live. Exposed for users who want to
145
+ * compose their own config.
72
146
  */
73
- declare const tailwindcssRules: {
74
- readonly 'tailwindcss/no-custom-classname': "off";
75
- readonly 'tailwindcss/classnames-order': "warn";
76
- readonly 'tailwindcss/no-contradicting-classname': "error";
77
- readonly 'tailwindcss/enforces-negative-arbitrary-values': "error";
78
- readonly 'tailwindcss/enforces-shorthand': "warn";
79
- };
147
+ declare const TAILWINDCSS_DEFAULT_FILES: string[];
148
+ /**
149
+ * Build the flat-config block(s) that wire up
150
+ * `eslint-plugin-better-tailwindcss`. The plugin already ships preset
151
+ * objects with `plugins` + `rules`; we spread them and add `files` plus
152
+ * `settings['better-tailwindcss']` so the user's options reach the plugin.
153
+ */
154
+ declare function tailwindcssConfigs(preset: TailwindcssPreset, settings: MazTailwindcssOptions): Linter.Config[];
80
155
 
81
156
  declare const vueRules: {
82
157
  'vue/custom-event-name-casing': ["error", "kebab-case"];
@@ -102,5 +177,5 @@ declare const vueRules: {
102
177
  */
103
178
  declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
104
179
 
105
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
106
- export type { MazESLintConfig, MazESLintOptions };
180
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules };
181
+ export type { MazESLintConfig, MazESLintOptions, MazTailwindcssOptions, TailwindcssPreset };
package/dist/index.mjs CHANGED
@@ -1,10 +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
- import tailwind from 'eslint-plugin-tailwindcss';
7
5
  import vueA11y from 'eslint-plugin-vuejs-accessibility';
6
+ import { createConsola } from 'consola';
7
+ import betterTailwindcss from 'eslint-plugin-better-tailwindcss';
8
8
 
9
9
  function baseRules(isProduction) {
10
10
  return {
@@ -72,6 +72,29 @@ const GLOBAL_IGNORES = [
72
72
  "**/components.d.ts"
73
73
  ];
74
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
+
75
98
  const markdown = {
76
99
  files: ["**/*.md/**"],
77
100
  rules: {
@@ -104,17 +127,34 @@ const sonarjsTestRules = {
104
127
  "sonarjs/no-duplicate-string": "off"
105
128
  };
106
129
 
107
- const tailwindcssRules = {
108
- // Allow custom class names (useful for component libraries)
109
- "tailwindcss/no-custom-classname": "off",
110
- // Enforce consistent class ordering
111
- "tailwindcss/classnames-order": "warn",
112
- // Prevent contradicting classes
113
- "tailwindcss/no-contradicting-classname": "error",
114
- // Enforce valid Tailwind syntax
115
- "tailwindcss/enforces-negative-arbitrary-values": "error",
116
- "tailwindcss/enforces-shorthand": "warn"
117
- };
130
+ const TAILWINDCSS_FILES = ["**/*.{js,jsx,cjs,mjs,ts,mts,cts,tsx,vue,html,svelte,astro,astrojs,css,scss}"];
131
+ const TAILWINDCSS_DEFAULT_FILES = TAILWINDCSS_FILES;
132
+ function tailwindcssConfigs(preset, settings) {
133
+ const presetConfig = betterTailwindcss.configs[preset];
134
+ const tailwindSettings = {};
135
+ if (settings.entryPoint)
136
+ tailwindSettings.entryPoint = settings.entryPoint;
137
+ if (settings.tailwindConfig)
138
+ tailwindSettings.tailwindConfig = settings.tailwindConfig;
139
+ if (settings.detectComponentClasses !== void 0)
140
+ tailwindSettings.detectComponentClasses = settings.detectComponentClasses;
141
+ if (settings.cwd)
142
+ tailwindSettings.cwd = settings.cwd;
143
+ if (settings.tsconfig)
144
+ tailwindSettings.tsconfig = settings.tsconfig;
145
+ return [{
146
+ ...presetConfig,
147
+ files: TAILWINDCSS_FILES,
148
+ settings: {
149
+ "better-tailwindcss": tailwindSettings
150
+ },
151
+ rules: {
152
+ ...presetConfig.rules,
153
+ "better-tailwindcss/no-unknown-classes": "off",
154
+ "better-tailwindcss/enforce-consistent-line-wrapping": "off"
155
+ }
156
+ }];
157
+ }
118
158
 
119
159
  const testRules = {
120
160
  "max-nested-callbacks": "off"
@@ -124,6 +164,7 @@ const vueRules = {
124
164
  "vue/custom-event-name-casing": ["error", "kebab-case"]
125
165
  };
126
166
 
167
+ const TAG = "[@maz-ui/eslint-config]";
127
168
  const defaultOptions = {
128
169
  formatters: true,
129
170
  typescript: true,
@@ -140,30 +181,89 @@ function getPackageJson() {
140
181
  return void 0;
141
182
  }
142
183
  }
143
- function getEslintMajorVersion() {
144
- try {
145
- const _require = createRequire(import.meta.url);
146
- const eslintPkg = _require("eslint/package.json");
147
- return Number(eslintPkg.version.split(".")[0]);
148
- } catch {
149
- return 0;
150
- }
184
+ function hasDependency(pkg, ...names) {
185
+ if (!pkg)
186
+ return false;
187
+ const buckets = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
188
+ return names.some((name) => buckets.some((bucket) => bucket && name in bucket));
151
189
  }
152
- function isVueOrNuxtProject() {
153
- const packageJson = getPackageJson();
154
- return packageJson?.dependencies?.vue || packageJson?.devDependencies?.vue || packageJson?.peerDependencies?.vue || packageJson?.dependencies?.nuxt || packageJson?.devDependencies?.nuxt || packageJson?.peerDependencies?.nuxt;
190
+ function resolveVue(option) {
191
+ if (option === true)
192
+ return { value: true, source: "explicit" };
193
+ if (option === false)
194
+ return { value: false, source: "explicit" };
195
+ return { value: hasDependency(getPackageJson(), "vue", "nuxt"), source: "auto-detected" };
196
+ }
197
+ function resolveTailwindcss(option) {
198
+ if (option === void 0)
199
+ return { value: false, source: "default" };
200
+ if (option === false)
201
+ return { value: false, source: "explicit" };
202
+ if (option === true)
203
+ return { value: { preset: "recommended", settings: {} }, source: "explicit" };
204
+ if (typeof option === "string")
205
+ return { value: { preset: option, settings: {} }, source: "explicit" };
206
+ return { value: { preset: option.preset ?? "recommended", settings: option }, source: "explicit" };
207
+ }
208
+ function resolveOptions(opts, env) {
209
+ const vue = resolveVue(opts.vue);
210
+ const tailwind = resolveTailwindcss(opts.tailwindcss);
211
+ return {
212
+ typescript: opts.typescript !== false,
213
+ formatters: opts.formatters !== false,
214
+ unicorn: opts.unicorn !== false,
215
+ sonarjs: opts.sonarjs !== false,
216
+ vue: vue.value,
217
+ vueSource: vue.source,
218
+ vueAccessibility: opts.vueAccessibility === true,
219
+ tailwindcss: tailwind.value,
220
+ tailwindcssSource: tailwind.source,
221
+ env
222
+ };
223
+ }
224
+ function formatSourceTag(source) {
225
+ if (source === "auto-detected")
226
+ return " (auto-detected)";
227
+ if (source === "default")
228
+ return " (default)";
229
+ return "";
230
+ }
231
+ function formatResolutionBox(r) {
232
+ const tailwindLabel = r.tailwindcss === false ? "off" : r.tailwindcss.preset;
233
+ const rows = [
234
+ ["typescript", String(r.typescript)],
235
+ ["vue", `${r.vue}${formatSourceTag(r.vueSource)}`],
236
+ ["vueAccessibility", String(r.vueAccessibility)],
237
+ ["sonarjs", String(r.sonarjs)],
238
+ ["tailwindcss", `${tailwindLabel}${formatSourceTag(r.tailwindcssSource)}`],
239
+ ["formatters", String(r.formatters)],
240
+ ["unicorn", String(r.unicorn)],
241
+ ["env", r.env]
242
+ ];
243
+ const labelWidth = Math.max(...rows.map(([k]) => k.length));
244
+ return rows.map(([k, v]) => `${k.padEnd(labelWidth)} ${v}`).join("\n");
155
245
  }
156
246
  function defineConfig(options = {}, ...userConfigs) {
247
+ const log = createLogger();
248
+ const defaultLogLevel = process.stdout.isTTY ? "default" : "silent";
249
+ log.setLevel(options.logLevel ?? defaultLogLevel);
157
250
  const opts = { ...defaultOptions, ...options, ignores: [...GLOBAL_IGNORES, ...options.ignores || []] };
158
251
  const env = opts.env || process.env.NODE_ENV || "production";
252
+ const resolved = resolveOptions(opts, env);
253
+ log.box({
254
+ title: "@maz-ui/eslint-config",
255
+ message: formatResolutionBox(resolved),
256
+ style: { borderColor: "cyan", padding: 1 }
257
+ });
159
258
  const additionalConfigs = [];
160
- if (opts.vue || isVueOrNuxtProject()) {
259
+ if (resolved.vue) {
161
260
  additionalConfigs.push({
162
261
  files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
163
262
  rules: vueRules
164
263
  });
264
+ log.debug(`${TAG} Vue: applied vueRules to JS/TS/Vue files`);
165
265
  }
166
- if (opts.sonarjs) {
266
+ if (resolved.sonarjs) {
167
267
  const sonarjsFiles = ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"];
168
268
  additionalConfigs.push({
169
269
  ...configs.recommended,
@@ -177,8 +277,9 @@ function defineConfig(options = {}, ...userConfigs) {
177
277
  files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
178
278
  rules: sonarjsTestRules
179
279
  });
280
+ log.debug(`${TAG} SonarJS: applied recommended preset + ${Object.keys(sonarjsRules).length} extra rules + test-file relaxations`);
180
281
  }
181
- if (opts.vueAccessibility) {
282
+ if (resolved.vueAccessibility) {
182
283
  const vueA11yConfigs = vueA11y.configs["flat/recommended"];
183
284
  const configsArray = Array.isArray(vueA11yConfigs) ? vueA11yConfigs : [vueA11yConfigs];
184
285
  const fixedConfigs = configsArray.map((config) => {
@@ -199,21 +300,26 @@ function defineConfig(options = {}, ...userConfigs) {
199
300
  return config;
200
301
  });
201
302
  additionalConfigs.push(...fixedConfigs);
303
+ log.debug(`${TAG} Vue a11y: applied ${fixedConfigs.length} flat-config block(s) from eslint-plugin-vuejs-accessibility`);
202
304
  }
203
- if (opts.tailwindcss) {
204
- if (getEslintMajorVersion() >= 10) {
205
- console.warn("[maz-eslint-config] eslint-plugin-tailwindcss is not compatible with ESLint 10+ (uses removed context.getSourceCode API). Tailwind CSS rules are disabled.");
206
- } else {
207
- additionalConfigs.push(...tailwind.configs["flat/recommended"]);
208
- additionalConfigs.push({
209
- rules: tailwindcssRules
210
- });
211
- }
305
+ if (resolved.tailwindcss) {
306
+ const blocks = tailwindcssConfigs(resolved.tailwindcss.preset, resolved.tailwindcss.settings);
307
+ additionalConfigs.push(...blocks);
308
+ const settingKeys = Object.keys(resolved.tailwindcss.settings).filter((k) => k !== "preset");
309
+ const settingsHint = settingKeys.length > 0 ? ` with settings (${settingKeys.join(", ")})` : "";
310
+ log.debug(`${TAG} Tailwind: loaded "${resolved.tailwindcss.preset}" preset${settingsHint}`);
212
311
  }
213
312
  additionalConfigs.push({
214
313
  files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
215
314
  rules: testRules
216
315
  });
316
+ log.debug(`${TAG} Tests: relaxed rules applied to *.spec.{ts,js} / *.test.{ts,js}`);
317
+ if (options.rules && Object.keys(options.rules).length > 0)
318
+ log.debug(`${TAG} User: merged ${Object.keys(options.rules).length} rule override(s)`);
319
+ if (userConfigs.length > 0)
320
+ log.debug(`${TAG} User: appended ${userConfigs.length} flat-config argument(s)`);
321
+ log.verbose(`${TAG} Final config block count: ${additionalConfigs.length + userConfigs.length + 1} (additional: ${additionalConfigs.length}, user: ${userConfigs.length}, +markdown)`);
322
+ log.verbose(`${TAG} Ignore globs: ${opts.ignores.length}`);
217
323
  return antfu({
218
324
  formatters: opts.formatters,
219
325
  ...opts,
@@ -227,4 +333,4 @@ function defineConfig(options = {}, ...userConfigs) {
227
333
  }, ...additionalConfigs, ...userConfigs, markdown);
228
334
  }
229
335
 
230
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
336
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@maz-ui/eslint-config",
3
3
  "type": "module",
4
- "version": "4.9.1",
4
+ "version": "5.0.0-beta.1",
5
5
  "description": "ESLint configuration for JavaScript/TypeScript projects",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",
@@ -43,14 +43,14 @@
43
43
  },
44
44
  "dependencies": {
45
45
  "@antfu/eslint-config": "^8.2.0",
46
+ "consola": "^3.4.2",
47
+ "eslint-plugin-better-tailwindcss": "^4.5.0",
46
48
  "eslint-plugin-format": "^2.0.1",
47
- "eslint-plugin-sonarjs": "^4.0.2",
48
- "eslint-plugin-tailwindcss": "^3.18.3",
49
+ "eslint-plugin-sonarjs": "^4.0.3",
49
50
  "eslint-plugin-vuejs-accessibility": "^2.5.0"
50
51
  },
51
52
  "devDependencies": {
52
- "@types/eslint-plugin-tailwindcss": "^3.17.0",
53
- "eslint": "^10.1.0",
53
+ "eslint": "^10.3.0",
54
54
  "unbuild": "^3.6.1"
55
55
  },
56
56
  "lint-staged": {
@@ -59,6 +59,7 @@
59
59
  ]
60
60
  },
61
61
  "scripts": {
62
+ "codegen": "nx build",
62
63
  "build": "unbuild",
63
64
  "dev": "unbuild --stub",
64
65
  "typecheck": "tsc --noEmit --skipLibCheck",