@shakuroinc/eslint-config-react 6.4.3 → 7.0.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,32 +1,227 @@
1
1
  # @shakuroinc/eslint-config-react
2
2
 
3
- [Shakuro](https://shakuro.com/) eslint (with React and tailwindcss support) and prettier extensible basic config.
3
+ [Shakuro](https://shakuro.com/) shared ESLint + Prettier config for React projects.
4
4
 
5
- ## Usage
5
+ **v7** is a flat-config rewrite targeting:
6
6
 
7
- ### Install package:
7
+ - ESLint 9 / 10 (flat config only — `eslint.config.js`)
8
+ - React 19
9
+ - Tailwind CSS v4 (`eslint-plugin-tailwindcss@4.x` beta)
10
+ - `eslint-plugin-react-hooks@7`
11
+ - `typescript-eslint@8`
12
+ - Prettier 3 with `prettier-plugin-tailwindcss@0.8`
8
13
 
9
- `yarn add -D @shakuroinc/eslint-config-react`
14
+ > Upgrading from v6? See [Migration from v6](#migration-from-v6) below.
10
15
 
11
- ### Install package dependencies:
16
+ ## Install
12
17
 
13
- `npx install-peerdeps @shakuroinc/eslint-config-react -d -Y`
18
+ ```sh
19
+ pnpm add -D @shakuroinc/eslint-config-react
20
+ ```
21
+
22
+ Install the peer dependencies. The config does not bundle plugins — your project
23
+ owns the versions, so updating a plugin doesn't require a new release of this
24
+ package.
25
+
26
+ ```sh
27
+ pnpm add -D \
28
+ eslint \
29
+ eslint-config-prettier \
30
+ eslint-plugin-prettier \
31
+ eslint-plugin-react-hooks \
32
+ eslint-plugin-simple-import-sort \
33
+ eslint-plugin-tailwindcss@beta \
34
+ globals \
35
+ prettier \
36
+ prettier-plugin-tailwindcss \
37
+ typescript-eslint
38
+ ```
39
+
40
+ Optional peers (only needed if you lint `.md` / `.mdx`):
41
+
42
+ ```sh
43
+ pnpm add -D eslint-mdx eslint-plugin-mdx
44
+ ```
45
+
46
+ ## Configure ESLint
47
+
48
+ The package exports a **factory function**. Call it with your project options
49
+ and spread the result into your flat config.
50
+
51
+ ```js
52
+ // eslint.config.js
53
+ const { resolve } = require('node:path');
54
+ const shakuroConfig = require('@shakuroinc/eslint-config-react');
55
+
56
+ module.exports = shakuroConfig({
57
+ tailwindStylesheet: resolve(__dirname, 'src/styles/globals.css'),
58
+ });
59
+ ```
60
+
61
+ ### Composing with other configs
62
+
63
+ `shakuroConfig()` returns an array, so you can prepend or append your own
64
+ blocks. A typical Next.js setup looks like this:
65
+
66
+ ```js
67
+ // eslint.config.js
68
+ const { resolve } = require('node:path');
69
+ const shakuroConfig = require('@shakuroinc/eslint-config-react');
70
+ const nextCoreWebVitals = require('eslint-config-next/core-web-vitals');
71
+
72
+ module.exports = [
73
+ ...nextCoreWebVitals,
74
+ ...shakuroConfig({
75
+ tailwindStylesheet: resolve(__dirname, 'src/styles/globals.css'),
76
+ tailwindCallees: ['cn', 'clsx', 'cva'],
77
+ next: { rootDir: 'apps/frontend/' },
78
+ }),
79
+ // Project-specific overrides — keep them after the shared config so they win.
80
+ {
81
+ rules: { 'max-lines': 'off' },
82
+ },
83
+ ];
84
+ ```
85
+
86
+ ### Factory options
87
+
88
+ | Option | Type | Default | Description |
89
+ | -------------------- | --------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
90
+ | `tailwindStylesheet` | `string` | `undefined` | Absolute path to your Tailwind v4 entry CSS file. Forwarded to the plugin's `config` setting. |
91
+ | `tailwindCallees` | `string[]` | `['cn', 'clsx', 'cva', 'tv']` | Function names whose string arguments contain Tailwind class names. |
92
+ | `tailwindWhitelist` | `string[]` | See [Default whitelist](#default-tailwind-whitelist) | Regex patterns ignored by `tailwindcss/no-custom-classname`. |
93
+ | `ignores` | `string[]` | `[]` | Extra globs appended to the default ignore list. |
94
+ | `reactVersion` | `string` | `'19.0'` | Pinned React version for `eslint-plugin-react`. **Do not set to `'detect'`** — it's broken under ESLint 10 in current plugin versions. |
95
+ | `next` | `{ rootDir: string }` | `undefined` | Forwarded to `settings.next` for `eslint-plugin-next`. |
96
+
97
+ ### Default ignores
98
+
99
+ ```text
100
+ **/node_modules/**
101
+ **/public/**
102
+ **/dist/**
103
+ **/dist_keycloak/**
104
+ **/.next/**
105
+ **/.turbo/**
106
+ **/coverage/**
107
+ **/storybook-static/**
108
+ **/next-env.d.ts
109
+ **/*.json.ts
110
+ **/importMap.js
111
+ ```
112
+
113
+ Append more via the `ignores` option.
114
+
115
+ ### Default Tailwind whitelist
116
+
117
+ ```text
118
+ pf-.*
119
+ g-recaptcha
120
+ cn-input-otp
121
+ -?translate-x-\\[[-]?50%\\]
122
+ ```
123
+
124
+ ### Named exports
125
+
126
+ The factory is also available under several named exports for advanced use:
127
+
128
+ ```js
129
+ const shakuroConfig = require('@shakuroinc/eslint-config-react');
130
+
131
+ const {
132
+ sharedRules, // The rule map applied to JS/TS/JSX/TSX files.
133
+ mdxRuleOverrides, // Rule overrides applied to .md/.mdx blocks.
134
+ DEFAULT_IGNORES,
135
+ DEFAULT_TAILWIND_CALLEES,
136
+ DEFAULT_TAILWIND_WHITELIST,
137
+ } = shakuroConfig;
138
+ ```
139
+
140
+ This is useful if you want to drop the rule set into your own custom flat-config
141
+ block instead of using the factory.
14
142
 
15
- > if you using yarn workspaces `npx install-peerdeps @shakuroinc/eslint-config-react -d -Y --extra-args "--ignore-workspace-root-check"`
143
+ ## Configure Prettier
16
144
 
17
- ### Configure `eslint`:
145
+ Most projects only need to point `tailwindStylesheet` at their own globals.css:
18
146
 
19
147
  ```js
20
- // .eslintrc.js
148
+ // prettier.config.js
21
149
  module.exports = {
22
- extends: ['@shakuroinc/eslint-config-react'],
23
- // extend config if needed
150
+ ...require('@shakuroinc/eslint-config-react/prettier'),
151
+ tailwindStylesheet: './src/styles/globals.css',
24
152
  };
25
153
  ```
26
154
 
27
- ### Configure prettier:
155
+ The base config sets:
28
156
 
29
157
  ```js
30
- // .prettierrc.js
31
- module.exports = { ...require('@shakuroinc/eslint-config-react/prettier.config') };
158
+ {
159
+ arrowParens: 'avoid',
160
+ bracketSpacing: true,
161
+ plugins: ['prettier-plugin-tailwindcss'],
162
+ printWidth: 100,
163
+ semi: true,
164
+ singleQuote: true,
165
+ tabWidth: 2,
166
+ trailingComma: 'all',
167
+ // prettier-plugin-tailwindcss (v4):
168
+ tailwindFunctions: ['cn', 'clsx', 'cva', 'tv'],
169
+ tailwindAttributes: ['className', 'classNames', '.*ClassName'],
170
+ }
32
171
  ```
172
+
173
+ ## What's included
174
+
175
+ - `typescript-eslint`'s `recommended` flat preset.
176
+ - `eslint-plugin-react-hooks` (legacy + v7 rules — only the classic ones are
177
+ pinned; v7 introduces new rules like `react-hooks/refs` and
178
+ `react-hooks/set-state-in-effect`, which are inherited at their plugin
179
+ defaults and surface real issues you should fix).
180
+ - `eslint-plugin-simple-import-sort` with the Shakuro import grouping
181
+ (`react`/`next` → vendors → `@shakuroinc`/`@sh` → project aliases → relative
182
+ → side-effect CSS).
183
+ - `eslint-plugin-tailwindcss@4` with class-order disabled (Prettier handles
184
+ ordering) and `no-contradicting-classname` enabled.
185
+ - `eslint-plugin-prettier` + `eslint-config-prettier` so formatting violations
186
+ surface as lint errors.
187
+ - MDX support via `eslint-plugin-mdx` (optional peer).
188
+ - A `padding-line-between-statements` rule preserving the previous Shakuro
189
+ spacing conventions.
190
+
191
+ ## Migration from v6
192
+
193
+ | v6 (legacy) | v7 (flat config) |
194
+ | ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- |
195
+ | `.eslintrc.js` with `extends: ['@shakuroinc/eslint-config-react']` | `eslint.config.js` calling `shakuroConfig({ ... })` (see above) |
196
+ | `main: '.eslintrc.js'` | `main: 'eslint.config.js'`, named export `./prettier` |
197
+ | ESLint ≥ 8.29 | ESLint ≥ 9 |
198
+ | `eslint-plugin-react@7.31.11`, `react-hooks@4` | `eslint-plugin-react@^7.37` (inherited from your config), `react-hooks@7` |
199
+ | `tailwindcss@3.x` plugin, class-order on | `tailwindcss@4.x-beta` plugin, class-order off (Prettier handles it) |
200
+ | `@typescript-eslint/padding-line-between-statements` | Stock `padding-line-between-statements` (TS-only selectors dropped — install `@stylistic/eslint-plugin` if you need them) |
201
+ | `prettier-plugin-tailwindcss@0.2` | `prettier-plugin-tailwindcss@0.8` with `tailwindStylesheet` for v4 |
202
+
203
+ ### Breaking rule changes
204
+
205
+ - `tailwindcss/classnames-order` is **off** — Prettier sorts classes via
206
+ `prettier-plugin-tailwindcss`. Run prettier `--write` once on the codebase
207
+ before enforcing.
208
+ - Legacy `eslint-plugin-import` overrides are dropped. If your project uses
209
+ `eslint-config-next`, the import plugin comes along for the ride; otherwise
210
+ add it yourself in a follow-up block.
211
+ - `naming-convention` no longer flags string-literal object keys or numeric
212
+ keys — pre-existing `// eslint-disable-next-line @typescript-eslint/naming-convention`
213
+ comments above those constructs will be reported as unused.
214
+ - `@typescript-eslint/no-var-requires` was renamed to
215
+ `@typescript-eslint/no-require-imports`. Update any local disable comments.
216
+
217
+ ## Give it a try and reach us
218
+
219
+ Explore our expertise in <a href="https://shakuro.com/services/native-mobile-development/?utm_source=github&utm_medium=repository&utm_campaign=eslint">Native Mobile Development</a> and <a href="https://shakuro.com/services/ios-dev/?utm_source=github&utm_medium=repository&utm_campaign=eslint">iOS Development</a>.</p>
220
+
221
+ If you need professional assistance with your mobile or web project, feel free to <a href="https://shakuro.com/get-in-touch/?utm_source=github&utm_medium=repository&utm_campaign=eslint">contact our team</a>
222
+ Override `tailwindFunctions` or `tailwindAttributes` only if your project uses
223
+ non-default helpers (e.g. `twMerge`) or custom class-name prop names.
224
+
225
+ ## License
226
+
227
+ MIT
@@ -0,0 +1,318 @@
1
+ // @ts-check
2
+ /**
3
+ * @shakuroinc/eslint-config-react — flat config for ESLint 9+.
4
+ *
5
+ * Usage:
6
+ * // eslint.config.js
7
+ * const shakuroConfig = require('@shakuroinc/eslint-config-react');
8
+ *
9
+ * module.exports = shakuroConfig({
10
+ * tailwindStylesheet: './src/styles/globals.css',
11
+ * ignores: ['custom/**'],
12
+ * });
13
+ *
14
+ * You can also spread the result and append your own blocks:
15
+ * module.exports = [
16
+ * ...shakuroConfig(),
17
+ * { rules: { 'no-console': 'off' } },
18
+ * ];
19
+ */
20
+
21
+ // @ts-ignore
22
+ const tseslint = require('typescript-eslint');
23
+ const prettierPlugin = require('eslint-plugin-prettier');
24
+ const prettierConfig = require('eslint-config-prettier');
25
+ const simpleImportSort = require('eslint-plugin-simple-import-sort');
26
+ const tailwindcss = require('eslint-plugin-tailwindcss');
27
+ const reactHooks = require('eslint-plugin-react-hooks');
28
+ const mdx = require('eslint-plugin-mdx');
29
+ const globals = require('globals');
30
+
31
+ const DEFAULT_IGNORES = [
32
+ '**/node_modules/**',
33
+ '**/public/**',
34
+ '**/dist/**',
35
+ '**/dist_keycloak/**',
36
+ '**/.next/**',
37
+ '**/.turbo/**',
38
+ '**/coverage/**',
39
+ '**/storybook-static/**',
40
+ '**/next-env.d.ts',
41
+ '**/*.json.ts',
42
+ '**/importMap.js',
43
+ ];
44
+
45
+ const DEFAULT_TAILWIND_CALLEES = ['cn', 'clsx', 'cva', 'tv'];
46
+
47
+ const DEFAULT_TAILWIND_WHITELIST = [
48
+ 'pf-.*',
49
+ 'g-recaptcha',
50
+ 'cn-input-otp',
51
+ '-?translate-x-\\[[-]?50%\\]',
52
+ ];
53
+
54
+ /** Shared lint rules (TypeScript + React + a11y + tailwind + prettier). */
55
+ const sharedRules = {
56
+ // typescript-eslint relaxations
57
+ '@typescript-eslint/ban-ts-comment': 'off',
58
+ '@typescript-eslint/explicit-function-return-type': 'off',
59
+ '@typescript-eslint/explicit-member-accessibility': 'off',
60
+ '@typescript-eslint/explicit-module-boundary-types': 'off',
61
+ '@typescript-eslint/no-empty-function': 'off',
62
+ '@typescript-eslint/no-explicit-any': 'off',
63
+ '@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^[_]*$' }],
64
+ 'no-unused-vars': 'off',
65
+
66
+ // Naming conventions
67
+ '@typescript-eslint/naming-convention': [
68
+ 'error',
69
+ {
70
+ selector: 'default',
71
+ format: ['PascalCase', 'camelCase'],
72
+ leadingUnderscore: 'forbid',
73
+ trailingUnderscore: 'forbid',
74
+ },
75
+ {
76
+ selector: ['variable'],
77
+ format: ['PascalCase', 'camelCase', 'UPPER_CASE'],
78
+ leadingUnderscore: 'forbid',
79
+ trailingUnderscore: 'forbid',
80
+ },
81
+ {
82
+ selector: ['property'],
83
+ format: null,
84
+ leadingUnderscore: 'forbid',
85
+ trailingUnderscore: 'forbid',
86
+ },
87
+ {
88
+ selector: ['default', 'variable', 'property'],
89
+ format: null,
90
+ leadingUnderscore: 'allow',
91
+ filter: '^[_]*$',
92
+ },
93
+ { selector: 'typeLike', format: ['PascalCase'] },
94
+ {
95
+ selector: ['enumMember', 'enum'],
96
+ format: ['camelCase', 'UPPER_CASE', 'snake_case', 'PascalCase'],
97
+ },
98
+ { selector: 'property', format: null, filter: '^__html$' },
99
+ ],
100
+
101
+ // Statement spacing
102
+ // NOTE: stock `padding-line-between-statements` does not support TS-only types
103
+ // like `interface`/`type`/`multiline-const`. We keep the rule on common
104
+ // statement kinds; consumers who want the TS-specific selectors can layer
105
+ // `@stylistic/padding-line-between-statements` on top.
106
+ 'padding-line-between-statements': [
107
+ 'error',
108
+ { blankLine: 'always', prev: '*', next: ['return'] },
109
+ { blankLine: 'always', prev: 'function', next: 'function' },
110
+ {
111
+ blankLine: 'always',
112
+ prev: 'import',
113
+ next: [
114
+ 'block',
115
+ 'block-like',
116
+ 'case',
117
+ 'class',
118
+ 'const',
119
+ 'export',
120
+ 'expression',
121
+ 'for',
122
+ 'function',
123
+ 'if',
124
+ 'let',
125
+ 'return',
126
+ 'throw',
127
+ 'try',
128
+ 'while',
129
+ ],
130
+ },
131
+ {
132
+ blankLine: 'always',
133
+ prev: [
134
+ 'import',
135
+ 'block',
136
+ 'block-like',
137
+ 'case',
138
+ 'class',
139
+ 'const',
140
+ 'expression',
141
+ 'for',
142
+ 'function',
143
+ 'if',
144
+ 'let',
145
+ 'return',
146
+ 'throw',
147
+ 'try',
148
+ 'while',
149
+ ],
150
+ next: 'export',
151
+ },
152
+ { blankLine: 'always', prev: 'directive', next: '*' },
153
+ ],
154
+
155
+ // Core JS
156
+ curly: ['error', 'multi-line'],
157
+ 'no-console': 'error',
158
+ 'no-duplicate-imports': 'error',
159
+ 'no-restricted-imports': [
160
+ 'error',
161
+ {
162
+ paths: ['ui'],
163
+ patterns: [
164
+ {
165
+ group: ['@sh/app', '@sh/app/*'],
166
+ message: 'Importing from `app` package is disallowed for architecture reasons',
167
+ },
168
+ ],
169
+ },
170
+ ],
171
+ quotes: ['error', 'single', { allowTemplateLiterals: false, avoidEscape: true }],
172
+ 'sort-imports': 'off',
173
+
174
+ // a11y
175
+ 'jsx-a11y/anchor-is-valid': [
176
+ 'error',
177
+ {
178
+ components: ['Link'],
179
+ specialLink: ['hrefLeft', 'hrefRight'],
180
+ aspects: ['invalidHref', 'preferButton'],
181
+ },
182
+ ],
183
+
184
+ // react
185
+ 'react/button-has-type': 'error',
186
+ 'react/display-name': 'off',
187
+ 'react/function-component-definition': 'off',
188
+ 'react/jsx-curly-brace-presence': [2, { props: 'never', children: 'never' }],
189
+ 'react/jsx-sort-props': [
190
+ 2,
191
+ { ignoreCase: true, callbacksLast: true, shorthandLast: false, reservedFirst: true },
192
+ ],
193
+ 'react/no-unescaped-entities': 'off',
194
+ 'react/prop-types': 'off',
195
+ 'react/react-in-jsx-scope': 'off',
196
+ 'react/self-closing-comp': 'warn',
197
+
198
+ // react-hooks (v7 introduces new rules; we enable the classic ones explicitly)
199
+ 'react-hooks/exhaustive-deps': 'error',
200
+ 'react-hooks/rules-of-hooks': 'error',
201
+
202
+ // simple-import-sort
203
+ 'simple-import-sort/exports': 'warn',
204
+ 'simple-import-sort/imports': [
205
+ 1,
206
+ {
207
+ groups: [
208
+ ['^\\u0000'],
209
+ ['^react', '^next', '^[^.]'],
210
+ ['^@shakuroinc/', '^@sh/'],
211
+ ['^@/', '^~/', '^#/', '^libs$', '^libs/', '^features/'],
212
+ ['^\\.\\.(?!/?$)', '^\\.\\./?$', '^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
213
+ ['^.+\\.s?css$'],
214
+ ],
215
+ },
216
+ ],
217
+
218
+ // tailwindcss
219
+ // Class order is handled by prettier-plugin-tailwindcss.
220
+ 'tailwindcss/classnames-order': 'off',
221
+ 'tailwindcss/no-contradicting-classname': 'error',
222
+ // Consumers override the whitelist via factory options.
223
+ 'tailwindcss/no-custom-classname': ['warn', { whitelist: DEFAULT_TAILWIND_WHITELIST }],
224
+
225
+ // Misc
226
+ 'max-lines': ['error', { max: 300, skipComments: true }],
227
+ };
228
+
229
+ const mdxRuleOverrides = {
230
+ '@next/next/no-img-element': 'off',
231
+ '@typescript-eslint/naming-convention': 'off',
232
+ 'padding-line-between-statements': 'off',
233
+ 'max-lines': 'off',
234
+ 'no-unused-expressions': 'off',
235
+ 'react/self-closing-comp': 'off',
236
+ };
237
+
238
+ /**
239
+ * Build the flat config array.
240
+ *
241
+ * @param {object} [options]
242
+ * @param {string} [options.tailwindStylesheet] Path to the Tailwind CSS entry file (Tailwind v4).
243
+ * @param {string[]} [options.tailwindCallees] Function names that accept Tailwind class strings.
244
+ * @param {string[]} [options.tailwindWhitelist] Patterns to whitelist for `no-custom-classname`.
245
+ * @param {string[]} [options.ignores] Extra ignore patterns appended to the defaults.
246
+ * @param {string} [options.reactVersion] Pinned React version for `eslint-plugin-react`.
247
+ * @param {{ rootDir?: string }} [options.next] Settings forwarded to `eslint-plugin-next`.
248
+ * @returns {import('eslint').Linter.FlatConfig[]}
249
+ */
250
+ function shakuroConfig(options = {}) {
251
+ const {
252
+ tailwindStylesheet,
253
+ tailwindCallees = DEFAULT_TAILWIND_CALLEES,
254
+ tailwindWhitelist = DEFAULT_TAILWIND_WHITELIST,
255
+ ignores = [],
256
+ reactVersion = '19.0',
257
+ next: nextSettings,
258
+ } = options;
259
+
260
+ /** @type {import('eslint').Linter.FlatConfig[]} */
261
+ const config = [
262
+ { ignores: [...DEFAULT_IGNORES, ...ignores] },
263
+
264
+ // typescript-eslint flat presets
265
+ ...tseslint.configs.recommended,
266
+
267
+ {
268
+ files: ['**/*.{js,jsx,mjs,ts,tsx,mts,cts}'],
269
+ plugins: {
270
+ 'simple-import-sort': simpleImportSort,
271
+ tailwindcss,
272
+ 'react-hooks': reactHooks,
273
+ prettier: prettierPlugin,
274
+ },
275
+ languageOptions: {
276
+ ecmaVersion: 'latest',
277
+ sourceType: 'module',
278
+ parserOptions: { ecmaFeatures: { jsx: true } },
279
+ globals: { ...globals.browser, ...globals.node, ...globals.es2024 },
280
+ },
281
+ settings: {
282
+ react: { version: reactVersion },
283
+ ...(nextSettings ? { next: nextSettings } : {}),
284
+ tailwindcss: {
285
+ callees: tailwindCallees,
286
+ ...(tailwindStylesheet ? { config: tailwindStylesheet } : {}),
287
+ officialSorting: true,
288
+ },
289
+ },
290
+ rules: {
291
+ ...sharedRules,
292
+ 'tailwindcss/no-custom-classname': ['warn', { whitelist: tailwindWhitelist }],
293
+ ...prettierConfig.rules,
294
+ 'prettier/prettier': 'error',
295
+ },
296
+ },
297
+
298
+ // MDX
299
+ {
300
+ files: ['**/*.{md,mdx}'],
301
+ ...mdx.flat,
302
+ rules: {
303
+ ...(mdx.flat.rules ?? {}),
304
+ ...mdxRuleOverrides,
305
+ },
306
+ },
307
+ ];
308
+
309
+ return config;
310
+ }
311
+
312
+ module.exports = shakuroConfig;
313
+ module.exports.default = shakuroConfig;
314
+ module.exports.sharedRules = sharedRules;
315
+ module.exports.mdxRuleOverrides = mdxRuleOverrides;
316
+ module.exports.DEFAULT_IGNORES = DEFAULT_IGNORES;
317
+ module.exports.DEFAULT_TAILWIND_CALLEES = DEFAULT_TAILWIND_CALLEES;
318
+ module.exports.DEFAULT_TAILWIND_WHITELIST = DEFAULT_TAILWIND_WHITELIST;
package/package.json CHANGED
@@ -1,7 +1,17 @@
1
1
  {
2
2
  "name": "@shakuroinc/eslint-config-react",
3
- "version": "6.4.3",
4
- "main": ".eslintrc.js",
3
+ "version": "7.0.1",
4
+ "description": "Shakuro shared ESLint + Prettier config (flat config, ESLint 9/10, React 19, Tailwind v4).",
5
+ "main": "eslint.config.js",
6
+ "exports": {
7
+ ".": "./eslint.config.js",
8
+ "./prettier": "./prettier.config.js"
9
+ },
10
+ "files": [
11
+ "eslint.config.js",
12
+ "prettier.config.js",
13
+ "README.md"
14
+ ],
5
15
  "author": "Shakuro team",
6
16
  "license": "MIT",
7
17
  "repository": {
@@ -9,52 +19,47 @@
9
19
  "url": "https://github.com/shakurocom/eslint-config-react"
10
20
  },
11
21
  "scripts": {
12
- "lint": "eslint . --ext .ts,.tsx,.js,.mdx,.md --ignore-path .gitignore",
13
22
  "release": "git push origin master --tags && npm publish --access public --new-version"
14
23
  },
15
- "dependencies": {},
16
- "devDependencies": {
17
- "@typescript-eslint/eslint-plugin": "^6.1.0",
18
- "@typescript-eslint/parser": "^6.1.0",
19
- "eslint": "^8.45.0",
20
- "eslint-config-prettier": "^8.8.0",
21
- "eslint-mdx": "^2.1.0",
22
- "eslint-plugin-mdx": "^2.1.0",
24
+ "peerDependencies": {
25
+ "eslint": "^9.0.0 || ^10.0.0",
26
+ "eslint-config-prettier": "^10.0.0",
27
+ "eslint-mdx": "^3.0.0",
28
+ "eslint-plugin-mdx": "^3.0.0",
23
29
  "eslint-plugin-prettier": "^5.0.0",
24
- "eslint-plugin-simple-import-sort": "^10.0.0",
25
- "eslint-plugin-tailwindcss": "^3.13.0",
30
+ "eslint-plugin-react-hooks": "^7.0.0",
31
+ "eslint-plugin-simple-import-sort": "^13.0.0",
32
+ "eslint-plugin-tailwindcss": "^4.0.0-beta.0",
33
+ "globals": "^15.0.0 || ^16.0.0 || ^17.0.0",
26
34
  "prettier": "^3.0.0",
27
- "prettier-plugin-tailwindcss": "^0.4.1",
28
- "react": "^18.2.0",
29
- "typescript": "^5.1.6"
35
+ "prettier-plugin-tailwindcss": "^0.6.0 || ^0.7.0 || ^0.8.0",
36
+ "typescript-eslint": "^8.0.0"
30
37
  },
31
- "peerDependencies": {
32
- "@typescript-eslint/eslint-plugin": "^5.45.0",
33
- "@typescript-eslint/parser": "^5.45.0",
34
- "eslint": "^8.29.0",
35
- "eslint-config-prettier": "^8.5.0",
36
- "eslint-import-resolver-typescript": "^3.5.2",
37
- "eslint-mdx": "^2.0.5",
38
- "eslint-plugin-import": "^2.26.0",
39
- "eslint-plugin-jsx-a11y": "^6.6.1",
40
- "eslint-plugin-mdx": "^2.0.5",
41
- "eslint-plugin-prettier": "^4.2.1",
42
- "eslint-plugin-react": "7.31.11",
43
- "eslint-plugin-react-hooks": "^4.6.0",
44
- "eslint-plugin-simple-import-sort": "^8.0.0",
45
- "eslint-plugin-tailwindcss": "^3.7.1",
46
- "prettier-plugin-tailwindcss": "^0.2.0"
47
- },
48
- "lint-staged": {
49
- "*.{js,ts,tsx}": "eslint"
50
- },
51
- "husky": {
52
- "hooks": {
53
- "pre-commit": "lint-staged"
38
+ "peerDependenciesMeta": {
39
+ "eslint-plugin-mdx": {
40
+ "optional": true
41
+ },
42
+ "eslint-mdx": {
43
+ "optional": true
44
+ },
45
+ "prettier-plugin-tailwindcss": {
46
+ "optional": true
54
47
  }
55
48
  },
56
- "volta": {
57
- "node": "18.12.1",
58
- "yarn": "1.18.0"
49
+ "devDependencies": {
50
+ "eslint": "^10.3.0",
51
+ "eslint-config-prettier": "^10.1.8",
52
+ "eslint-mdx": "^3.7.0",
53
+ "eslint-plugin-mdx": "^3.7.0",
54
+ "eslint-plugin-prettier": "^5.5.5",
55
+ "eslint-plugin-react-hooks": "^7.1.1",
56
+ "eslint-plugin-simple-import-sort": "^13.0.0",
57
+ "eslint-plugin-tailwindcss": "4.0.0-beta.0",
58
+ "globals": "^16.5.0",
59
+ "prettier": "^3.8.3",
60
+ "prettier-plugin-tailwindcss": "^0.8.0",
61
+ "react": "^19.2.6",
62
+ "typescript": "^6.0.3",
63
+ "typescript-eslint": "^8.59.3"
59
64
  }
60
65
  }
@@ -1,4 +1,20 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
1
+ /**
2
+ * Shared Prettier base config.
3
+ *
4
+ * Tailwind v4 keys (consumed by `prettier-plugin-tailwindcss`) are set to
5
+ * sensible Shakuro defaults. Consumers usually only need to override
6
+ * `tailwindStylesheet` to point at their own globals.css:
7
+ *
8
+ * const base = require('@shakuroinc/eslint-config-react/prettier');
9
+ * module.exports = {
10
+ * ...base,
11
+ * tailwindStylesheet: './packages/ui/src/styles/globals.css',
12
+ * };
13
+ *
14
+ * Override `tailwindFunctions` / `tailwindAttributes` only if your project uses
15
+ * non-default helpers (e.g. `twMerge`, custom class-name props).
16
+ */
17
+ /** @type {import('prettier').Config} */
2
18
  module.exports = {
3
19
  arrowParens: 'avoid',
4
20
  bracketSpacing: true,
@@ -8,4 +24,6 @@ module.exports = {
8
24
  singleQuote: true,
9
25
  tabWidth: 2,
10
26
  trailingComma: 'all',
27
+ tailwindFunctions: ['cn', 'clsx', 'cva', 'tv'],
28
+ tailwindAttributes: ['className', 'classNames', '.*ClassName'],
11
29
  };
package/.eslintrc.js DELETED
@@ -1,255 +0,0 @@
1
- // @ts-check
2
- /* eslint-disable @typescript-eslint/naming-convention */
3
- module.exports = {
4
- parser: '@typescript-eslint/parser',
5
-
6
- plugins: [
7
- '@typescript-eslint',
8
- 'react',
9
- 'react-hooks',
10
- 'jsx-a11y',
11
- 'simple-import-sort',
12
- 'tailwindcss',
13
- ],
14
-
15
- extends: [
16
- 'plugin:@typescript-eslint/recommended',
17
- 'plugin:jsx-a11y/recommended',
18
- 'plugin:prettier/recommended',
19
- 'plugin:react/recommended',
20
- ],
21
-
22
- parserOptions: {
23
- ecmaVersion: 'latest',
24
- sourceType: 'module',
25
- ecmaFeatures: {
26
- jsx: true,
27
- },
28
- },
29
-
30
- settings: {
31
- react: {
32
- version: 'detect',
33
- },
34
- tailwindcss: {
35
- officialSorting: true,
36
- },
37
- },
38
-
39
- rules: {
40
- '@typescript-eslint/ban-ts-ignore': 'off',
41
- '@typescript-eslint/explicit-function-return-type': 'off',
42
- '@typescript-eslint/explicit-member-accessibility': 'off',
43
- '@typescript-eslint/explicit-module-boundary-types': 'off',
44
- '@typescript-eslint/interface-name-prefix': 'off',
45
- '@typescript-eslint/naming-convention': [
46
- 'error',
47
- {
48
- selector: 'default',
49
- format: ['PascalCase', 'camelCase'],
50
- leadingUnderscore: 'forbid',
51
- trailingUnderscore: 'forbid',
52
- },
53
- {
54
- selector: ['variable'],
55
- format: ['PascalCase', 'camelCase', 'UPPER_CASE'],
56
- leadingUnderscore: 'forbid',
57
- trailingUnderscore: 'forbid',
58
- },
59
- {
60
- selector: ['property'],
61
- format: null,
62
- leadingUnderscore: 'forbid',
63
- trailingUnderscore: 'forbid',
64
- },
65
- {
66
- selector: ['default', 'variable', 'property'],
67
- format: null,
68
- leadingUnderscore: 'allow',
69
- filter: '^[_]*$',
70
- },
71
- {
72
- selector: 'typeLike',
73
- format: ['PascalCase'],
74
- },
75
- {
76
- selector: ['enumMember', 'enum'],
77
- format: ['camelCase', 'UPPER_CASE', 'snake_case', 'PascalCase'],
78
- },
79
- {
80
- selector: 'property',
81
- format: null,
82
- filter: '^__html$',
83
- },
84
- ],
85
- '@typescript-eslint/no-empty-function': 'off',
86
- 'no-unused-vars': 'off',
87
- '@typescript-eslint/no-unused-vars': ['warn', { 'argsIgnorePattern': '^[_]*$' }],
88
- '@typescript-eslint/no-explicit-any': 'off',
89
- '@typescript-eslint/no-object-literal-type-assertion': 'off',
90
- '@typescript-eslint/padding-line-between-statements': [
91
- 'error',
92
- {
93
- blankLine: 'always',
94
- prev: ['interface', 'type'],
95
- next: '*',
96
- },
97
- { blankLine: 'always', prev: '*', next: ['return'] },
98
- { blankLine: 'always', prev: 'function', next: 'function' },
99
- { blankLine: 'always', prev: 'multiline-const', next: '*' },
100
- {
101
- blankLine: 'always',
102
- prev: 'import',
103
- next: [
104
- 'interface',
105
- 'type',
106
- 'block',
107
- 'block-like',
108
- 'case',
109
- 'class',
110
- 'const',
111
- 'export',
112
- 'expression',
113
- 'for',
114
- 'function',
115
- 'if',
116
- 'let',
117
- 'return',
118
- 'throw',
119
- 'try',
120
- 'while',
121
- ],
122
- },
123
- {
124
- blankLine: 'always',
125
- prev: [
126
- 'import',
127
- 'interface',
128
- 'type',
129
- 'block',
130
- 'block-like',
131
- 'case',
132
- 'class',
133
- 'const',
134
- 'expression',
135
- 'for',
136
- 'function',
137
- 'if',
138
- 'let',
139
- 'return',
140
- 'throw',
141
- 'try',
142
- 'while',
143
- ],
144
- next: 'export',
145
- },
146
- { blankLine: 'always', prev: 'directive', next: '*' },
147
- ],
148
- 'jsx-a11y/anchor-is-valid': [
149
- 'error',
150
- {
151
- components: ['Link'],
152
- specialLink: ['hrefLeft', 'hrefRight'],
153
- aspects: ['invalidHref', 'preferButton'],
154
- },
155
- ],
156
- curly: ['error', 'multi-line'],
157
- 'no-duplicate-imports': 'error',
158
- 'no-restricted-imports': [
159
- 'error',
160
- {
161
- paths: ['ui'],
162
- patterns: [
163
- {
164
- group: ['@sh/app', '@sh/app/*'],
165
- message: 'Importing from `app` package is disallowed for architecture reasons',
166
- },
167
- ],
168
- },
169
- ],
170
- 'react-hooks/exhaustive-deps': 'error',
171
- 'react-hooks/rules-of-hooks': 'error',
172
- 'react/button-has-type': 'error',
173
- 'react/display-name': 'off',
174
- 'react/no-unescaped-entities': 'off',
175
- 'react/prop-types': 'off',
176
- 'react/react-in-jsx-scope': 'off',
177
- 'react/self-closing-comp': 'warn',
178
- 'sort-imports': 'off',
179
- 'react/function-component-definition': "off",
180
- 'react/jsx-curly-brace-presence': [
181
- 2,
182
- {
183
- props: 'never',
184
- children: 'never',
185
- },
186
- ],
187
- 'react/jsx-sort-props': [
188
- 2,
189
- { ignoreCase: true, callbacksLast: true, shorthandLast: false, reservedFirst: true },
190
- ],
191
- 'max-lines': [
192
- 'error',
193
- {
194
- max: 300,
195
- skipComments: true,
196
- },
197
- ],
198
- 'no-console': 'error',
199
- 'simple-import-sort/imports': [
200
- 1,
201
- {
202
- groups: [
203
- ['^\\u0000'],
204
- ['^react', '^next', '^[^.]'],
205
- ['^@shakuroinc/', '^@sh/'],
206
- ['^@/', '^~/', '^#/', '^libs$', '^libs/', '^features/'],
207
- ['^\\.\\.(?!/?$)', '^\\.\\./?$', '^\\./(?=.*/)(?!/?$)', '^\\.(?!/?$)', '^\\./?$'],
208
- ['^.+\\.s?css$'],
209
- ],
210
- },
211
- ],
212
- 'simple-import-sort/exports': 'warn',
213
- 'tailwindcss/classnames-order': 'error',
214
- 'tailwindcss/no-contradicting-classname': 'error',
215
- 'tailwindcss/no-custom-classname': 'warn',
216
- quotes: ['error', 'single', { allowTemplateLiterals: false, avoidEscape: true }],
217
- },
218
-
219
- overrides: [
220
- {
221
- files: ['*js?x', '*ts?x'],
222
- extends: ['plugin:import/recommended', 'plugin:import/typescript'],
223
- rules: {
224
- 'import/order': 'off',
225
- 'import/no-restricted-paths': [
226
- 'error',
227
- {
228
- zones: [
229
- {
230
- target: 'ui',
231
- from: 'app',
232
- message: 'Importing from `app` package is disallowed for architecture reasons',
233
- },
234
- ],
235
- },
236
- ],
237
- },
238
- },
239
- {
240
- files: ['*.mdx', '*.md'],
241
- extends: 'plugin:mdx/recommended',
242
- parserOptions: {
243
- ecmaVersion: 'latest',
244
- },
245
- rules: {
246
- '@next/next/no-img-element': 'off',
247
- '@typescript-eslint/naming-convention': 'off',
248
- '@typescript-eslint/padding-line-between-statements': 'off',
249
- 'max-lines': 'off',
250
- 'no-unused-expressions': 'off',
251
- 'react/self-closing-comp': 'off',
252
- },
253
- },
254
- ],
255
- };
@@ -1,42 +0,0 @@
1
- {
2
- "typescript.tsdk": "node_modules/typescript/lib",
3
- "eslint.validate": [
4
- "markdown",
5
- "mdx",
6
- "javascript",
7
- "javascriptreact",
8
- "typescript",
9
- "typescriptreact"
10
- ],
11
- "eslint.options": {
12
- "extensions": [".mdx", ".js", ".jsx", ".ts", ".tsx"]
13
- },
14
- "editor.formatOnSave": true,
15
- "[javascript]": {
16
- "editor.formatOnSave": false
17
- },
18
- "[javascriptreact]": {
19
- "editor.formatOnSave": false
20
- },
21
- "[typescript]": {
22
- "editor.formatOnSave": false
23
- },
24
- "[typescriptreact]": {
25
- "editor.formatOnSave": false
26
- },
27
- "editor.codeActionsOnSave": {
28
- "eslint.autoFixOnSave": true,
29
- "source.fixAll.eslint": true
30
- },
31
- "window.title": "@shakuroinc/eslint-config-react | ${activeEditorShort}${separator}${rootName}",
32
- "tailwindCSS.emmetCompletions": true,
33
- "tailwindCSS.experimental.classRegex": [
34
- "ClassName='([^']*)", // prefixClassName='...'
35
- "ClassName=\"([^']*)", // prefixClassName="..."
36
- ["clsx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] // clsx(...)
37
- ],
38
- "cSpell.words": [
39
- "clsx",
40
- "shakuroinc"
41
- ]
42
- }