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

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,24 +141,24 @@ declare const sonarjsTestRules: {
68
141
  };
69
142
 
70
143
  /**
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).
144
+ * Files where Tailwind classes commonly live. Exposed for users who want to
145
+ * compose their own config.
77
146
  */
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
- };
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[];
85
155
 
86
156
  declare const vueRules: {
87
157
  'vue/custom-event-name-casing': ["error", "kebab-case"];
88
158
  };
159
+ declare const vueSfcOnlyRules: {
160
+ 'no-useless-assignment': "off";
161
+ };
89
162
 
90
163
  /**
91
164
  * Create ESLint configuration for Maz-UI and JavaScript/TypeScript projects
@@ -107,5 +180,5 @@ declare const vueRules: {
107
180
  */
108
181
  declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
109
182
 
110
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
111
- export type { MazESLintConfig, MazESLintOptions };
183
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
184
+ 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,24 +141,24 @@ declare const sonarjsTestRules: {
68
141
  };
69
142
 
70
143
  /**
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).
144
+ * Files where Tailwind classes commonly live. Exposed for users who want to
145
+ * compose their own config.
77
146
  */
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
- };
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[];
85
155
 
86
156
  declare const vueRules: {
87
157
  'vue/custom-event-name-casing': ["error", "kebab-case"];
88
158
  };
159
+ declare const vueSfcOnlyRules: {
160
+ 'no-useless-assignment': "off";
161
+ };
89
162
 
90
163
  /**
91
164
  * Create ESLint configuration for Maz-UI and JavaScript/TypeScript projects
@@ -107,5 +180,5 @@ declare const vueRules: {
107
180
  */
108
181
  declare function defineConfig(options?: MazESLintOptions, ...userConfigs: MazESLintUserConfig[]): MazESLintConfig;
109
182
 
110
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
111
- export type { MazESLintConfig, MazESLintOptions };
183
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssConfigs, vueRules, vueSfcOnlyRules };
184
+ 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,17 +127,34 @@ 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"
116
- };
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
+ }
117
158
 
118
159
  const testRules = {
119
160
  "max-nested-callbacks": "off"
@@ -122,7 +163,11 @@ const testRules = {
122
163
  const vueRules = {
123
164
  "vue/custom-event-name-casing": ["error", "kebab-case"]
124
165
  };
166
+ const vueSfcOnlyRules = {
167
+ "no-useless-assignment": "off"
168
+ };
125
169
 
170
+ const TAG = "[@maz-ui/eslint-config]";
126
171
  const defaultOptions = {
127
172
  formatters: true,
128
173
  typescript: true,
@@ -139,39 +184,93 @@ function getPackageJson() {
139
184
  return void 0;
140
185
  }
141
186
  }
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
- }
187
+ function hasDependency(pkg, ...names) {
188
+ if (!pkg)
189
+ return false;
190
+ const buckets = [pkg.dependencies, pkg.devDependencies, pkg.peerDependencies];
191
+ return names.some((name) => buckets.some((bucket) => bucket && name in bucket));
150
192
  }
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
- }
193
+ function resolveVue(option) {
194
+ if (option === true)
195
+ return { value: true, source: "explicit" };
196
+ if (option === false)
197
+ return { value: false, source: "explicit" };
198
+ return { value: hasDependency(getPackageJson(), "vue", "nuxt"), source: "auto-detected" };
199
+ }
200
+ function resolveTailwindcss(option) {
201
+ if (option === void 0)
202
+ return { value: false, source: "default" };
203
+ if (option === false)
204
+ return { value: false, source: "explicit" };
205
+ if (option === true)
206
+ return { value: { preset: "recommended", settings: {} }, source: "explicit" };
207
+ if (typeof option === "string")
208
+ return { value: { preset: option, settings: {} }, source: "explicit" };
209
+ return { value: { preset: option.preset ?? "recommended", settings: option }, source: "explicit" };
210
+ }
211
+ function resolveOptions(opts, env) {
212
+ const vue = resolveVue(opts.vue);
213
+ const tailwind = resolveTailwindcss(opts.tailwindcss);
214
+ return {
215
+ typescript: opts.typescript !== false,
216
+ formatters: opts.formatters !== false,
217
+ unicorn: opts.unicorn !== false,
218
+ sonarjs: opts.sonarjs !== false,
219
+ vue: vue.value,
220
+ vueSource: vue.source,
221
+ vueAccessibility: opts.vueAccessibility === true,
222
+ tailwindcss: tailwind.value,
223
+ tailwindcssSource: tailwind.source,
224
+ env
225
+ };
159
226
  }
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;
227
+ function formatSourceTag(source) {
228
+ if (source === "auto-detected")
229
+ return " (auto-detected)";
230
+ if (source === "default")
231
+ return " (default)";
232
+ return "";
233
+ }
234
+ function formatResolutionBox(r) {
235
+ const tailwindLabel = r.tailwindcss === false ? "off" : r.tailwindcss.preset;
236
+ const rows = [
237
+ ["typescript", String(r.typescript)],
238
+ ["vue", `${r.vue}${formatSourceTag(r.vueSource)}`],
239
+ ["vueAccessibility", String(r.vueAccessibility)],
240
+ ["sonarjs", String(r.sonarjs)],
241
+ ["tailwindcss", `${tailwindLabel}${formatSourceTag(r.tailwindcssSource)}`],
242
+ ["formatters", String(r.formatters)],
243
+ ["unicorn", String(r.unicorn)],
244
+ ["env", r.env]
245
+ ];
246
+ const labelWidth = Math.max(...rows.map(([k]) => k.length));
247
+ return rows.map(([k, v]) => `${k.padEnd(labelWidth)} ${v}`).join("\n");
163
248
  }
164
249
  function defineConfig(options = {}, ...userConfigs) {
250
+ const log = createLogger();
251
+ const defaultLogLevel = process.stdout.isTTY ? "default" : "silent";
252
+ log.setLevel(options.logLevel ?? defaultLogLevel);
165
253
  const opts = { ...defaultOptions, ...options, ignores: [...GLOBAL_IGNORES, ...options.ignores || []] };
166
254
  const env = opts.env || process.env.NODE_ENV || "production";
255
+ const resolved = resolveOptions(opts, env);
256
+ log.box({
257
+ title: "@maz-ui/eslint-config",
258
+ message: formatResolutionBox(resolved),
259
+ style: { borderColor: "cyan", padding: 1 }
260
+ });
167
261
  const additionalConfigs = [];
168
- if (opts.vue || isVueOrNuxtProject()) {
262
+ if (resolved.vue) {
169
263
  additionalConfigs.push({
170
264
  files: ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"],
171
265
  rules: vueRules
172
266
  });
267
+ additionalConfigs.push({
268
+ files: ["**/*.vue"],
269
+ rules: vueSfcOnlyRules
270
+ });
271
+ log.debug(`${TAG} Vue: applied vueRules to JS/TS/Vue files + SFC-only overrides to *.vue`);
173
272
  }
174
- if (opts.sonarjs) {
273
+ if (resolved.sonarjs) {
175
274
  const sonarjsFiles = ["**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx,vue}"];
176
275
  additionalConfigs.push({
177
276
  ...configs.recommended,
@@ -185,8 +284,9 @@ function defineConfig(options = {}, ...userConfigs) {
185
284
  files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
186
285
  rules: sonarjsTestRules
187
286
  });
287
+ log.debug(`${TAG} SonarJS: applied recommended preset + ${Object.keys(sonarjsRules).length} extra rules + test-file relaxations`);
188
288
  }
189
- if (opts.vueAccessibility) {
289
+ if (resolved.vueAccessibility) {
190
290
  const vueA11yConfigs = vueA11y.configs["flat/recommended"];
191
291
  const configsArray = Array.isArray(vueA11yConfigs) ? vueA11yConfigs : [vueA11yConfigs];
192
292
  const fixedConfigs = configsArray.map((config) => {
@@ -207,28 +307,26 @@ function defineConfig(options = {}, ...userConfigs) {
207
307
  return config;
208
308
  });
209
309
  additionalConfigs.push(...fixedConfigs);
310
+ log.debug(`${TAG} Vue a11y: applied ${fixedConfigs.length} flat-config block(s) from eslint-plugin-vuejs-accessibility`);
210
311
  }
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
- }
312
+ if (resolved.tailwindcss) {
313
+ const blocks = tailwindcssConfigs(resolved.tailwindcss.preset, resolved.tailwindcss.settings);
314
+ additionalConfigs.push(...blocks);
315
+ const settingKeys = Object.keys(resolved.tailwindcss.settings).filter((k) => k !== "preset");
316
+ const settingsHint = settingKeys.length > 0 ? ` with settings (${settingKeys.join(", ")})` : "";
317
+ log.debug(`${TAG} Tailwind: loaded "${resolved.tailwindcss.preset}" preset${settingsHint}`);
227
318
  }
228
319
  additionalConfigs.push({
229
320
  files: ["**/*.spec.ts", "**/*.test.ts", "**/*.spec.js", "**/*.test.js"],
230
321
  rules: testRules
231
322
  });
323
+ log.debug(`${TAG} Tests: relaxed rules applied to *.spec.{ts,js} / *.test.{ts,js}`);
324
+ if (options.rules && Object.keys(options.rules).length > 0)
325
+ log.debug(`${TAG} User: merged ${Object.keys(options.rules).length} rule override(s)`);
326
+ if (userConfigs.length > 0)
327
+ log.debug(`${TAG} User: appended ${userConfigs.length} flat-config argument(s)`);
328
+ log.verbose(`${TAG} Final config block count: ${additionalConfigs.length + userConfigs.length + 1} (additional: ${additionalConfigs.length}, user: ${userConfigs.length}, +markdown)`);
329
+ log.verbose(`${TAG} Ignore globs: ${opts.ignores.length}`);
232
330
  return antfu({
233
331
  formatters: opts.formatters,
234
332
  ...opts,
@@ -242,4 +340,4 @@ function defineConfig(options = {}, ...userConfigs) {
242
340
  }, ...additionalConfigs, ...userConfigs, markdown);
243
341
  }
244
342
 
245
- export { baseRules, defineConfig, sonarjsRules, sonarjsTestRules, tailwindcssRules, vueRules };
343
+ export { TAILWINDCSS_DEFAULT_FILES, baseRules, defineConfig, 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.2",
5
5
  "description": "ESLint configuration for JavaScript/TypeScript projects",
6
6
  "author": "Louis Mazel <me@loicmazuel.com>",
7
7
  "license": "MIT",
@@ -39,24 +39,18 @@
39
39
  "node": ">=20.19.0"
40
40
  },
41
41
  "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
- }
42
+ "eslint": ">=9.0.0 <11.0.0"
49
43
  },
50
44
  "dependencies": {
51
45
  "@antfu/eslint-config": "^8.2.0",
46
+ "consola": "^3.4.2",
47
+ "eslint-plugin-better-tailwindcss": "^4.5.0",
52
48
  "eslint-plugin-format": "^2.0.1",
53
49
  "eslint-plugin-sonarjs": "^4.0.3",
54
50
  "eslint-plugin-vuejs-accessibility": "^2.5.0"
55
51
  },
56
52
  "devDependencies": {
57
- "@types/eslint-plugin-tailwindcss": "^3.17.0",
58
53
  "eslint": "^10.3.0",
59
- "eslint-plugin-tailwindcss": "^3.18.3",
60
54
  "unbuild": "^3.6.1"
61
55
  },
62
56
  "lint-staged": {