@santux/eslint-config 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,85 @@
1
- # Temporary Holding Version
1
+ # @santux/eslint-config
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Shared ESLint, Prettier and commitlint config for TypeScript projects.
4
+
5
+ ## Entries
6
+
7
+ | Entry | Tool | Export |
8
+ | --------------------------------------- | ---------- | ----------------- |
9
+ | `@santux/eslint-config/lint` | ESLint 9 | Flat config array |
10
+ | `@santux/eslint-config/next` | ESLint 9 | Flat config array |
11
+ | `@santux/eslint-config/react-vite` | ESLint 9 | Flat config array |
12
+ | `@santux/eslint-config/tanstack-start` | ESLint 9 | Flat config array |
13
+ | `@santux/eslint-config/astro` | ESLint 9 | Flat config array |
14
+ | `@santux/eslint-config/astro-react` | ESLint 9 | Flat config array |
15
+ | `@santux/eslint-config/tailwind` | ESLint 9 | Flat config array |
16
+ | `@santux/eslint-config/format` | Prettier | Config object |
17
+ | `@santux/eslint-config/format-astro` | Prettier | Config object |
18
+ | `@santux/eslint-config/format-tailwind` | Prettier | Config object |
19
+ | `@santux/eslint-config/commitlint` | commitlint | Config object |
20
+
21
+ The lint entry uses typescript-eslint `strictTypeChecked` + `stylisticTypeChecked`, `eslint-plugin-import-x` (with the TypeScript import resolver, so `no-cycle`, `no-self-import` and `no-extraneous-dependencies` work on `.ts`/`.tsx`), unicorn and check-file naming.
22
+
23
+ The `next` entry builds on the lint entry with react, react-hooks, jsx-a11y and Next.js rules (`react.version` is `19`). Compose it after the base: `defineConfig([santux, next])`. Its plugins (`@next/eslint-plugin-next`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `globals`) are optional peer dependencies, needed only when you use it.
24
+
25
+ The `react-vite` entry builds on the lint entry the same way, with react, react-hooks, jsx-a11y and react-refresh rules. Its plugins (`eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `eslint-plugin-react-refresh`, `globals`) are optional peer dependencies.
26
+
27
+ The `tanstack-start` entry builds on the lint entry the same way, with react, react-hooks and jsx-a11y rules and browser plus Node globals. Its plugins (`eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-jsx-a11y`, `globals`) are optional peer dependencies.
28
+
29
+ The `astro` entry builds on the lint entry with the `eslint-plugin-astro` recommended rules and its jsx-a11y rules for `.astro` files, plus browser and Node globals. It parses `.astro` files with TypeScript and runs the type-aware rules there too, allows the `Props` interface Astro reads component props from, ignores `.astro/` and turns off the file and folder name rules under `pages`. Its plugins (`eslint-plugin-astro`, `eslint-plugin-jsx-a11y`, `globals`) are optional peer dependencies. `eslint-plugin-astro` stays on 1.7.0, the last version that supports ESLint 9, until this package moves to ESLint 10.
30
+
31
+ The `astro-react` entry adds the react, react-hooks and jsx-a11y rules to the `.jsx` and `.tsx` files of an Astro project with React islands (`@astrojs/react`), without react-refresh. Compose it after the `astro` entry: `defineConfig([santux, astro, astroReact])`. It needs `eslint-plugin-react` and `eslint-plugin-react-hooks`, optional peer dependencies.
32
+
33
+ The `tailwind` entry adds [`@shadcn/lint`](https://github.com/shadcn-ui/lint) (an optional peer dependency) with every rule except `require-static-classes`: `no-unknown-classes`, `no-raw-colors`, `no-arbitrary-values`, `no-inline-styles` and `no-restyle`. It needs no shadcn/ui: it finds the stylesheet that imports Tailwind, and it reads `components.json` when there is one. `no-restyle` only knows your design system components through `components.json` or `settings.shadcn.componentImports`. Compose it after the Framework entry: `defineConfig([santux, next, tailwind])`.
34
+
35
+ The `format-tailwind` entry is a Prettier config fragment that sorts Tailwind classes with `prettier-plugin-tailwindcss` (an optional peer dependency). Spread it after the format entry: `export default { ...santux, ...formatTailwind }`. For Tailwind v4 projects with a custom stylesheet, also set `tailwindStylesheet` in your Prettier config.
36
+
37
+ The `format-astro` entry formats `.astro` files with `prettier-plugin-astro` (an optional peer dependency), pinned to 0.14.1 because `prettier-plugin-tailwindcss` does not sort classes in `.astro` files with its 1.x. Spreading two entries keeps only the last `plugins`, so merge them with the Tailwind plugin last:
38
+
39
+ ```js
40
+ export default {
41
+ ...santux,
42
+ ...formatAstro,
43
+ ...formatTailwind,
44
+ plugins: [...formatAstro.plugins, ...formatTailwind.plugins],
45
+ }
46
+ ```
47
+
48
+ ## Usage
49
+
50
+ Every tool is an exact-version peer dependency and must be installed in your project:
51
+
52
+ ```sh
53
+ pnpm add -D @santux/eslint-config @commitlint/cli @commitlint/config-conventional @eslint/js @stylistic/eslint-plugin eslint eslint-config-prettier eslint-import-resolver-typescript eslint-plugin-check-file eslint-plugin-import-x eslint-plugin-unicorn husky prettier typescript typescript-eslint
54
+ ```
55
+
56
+ ```js
57
+ // eslint.config.js
58
+ import santux from '@santux/eslint-config/lint'
59
+ import { defineConfig } from 'eslint/config'
60
+
61
+ export default defineConfig([
62
+ santux,
63
+ // Override, disable or add rules locally
64
+ { rules: { 'no-console': 'off', 'unicorn/prefer-node-protocol': 'error' } },
65
+ { files: ['**/*.test.ts'], rules: { '@typescript-eslint/no-non-null-assertion': 'off' } },
66
+ ])
67
+ ```
68
+
69
+ ```js
70
+ // prettier.config.js
71
+ import santux from '@santux/eslint-config/format'
72
+
73
+ export default { ...santux, printWidth: 120 }
74
+ ```
75
+
76
+ ```js
77
+ // commitlint.config.js
78
+ export { default } from '@santux/eslint-config/commitlint'
79
+ ```
80
+
81
+ Requires Node `>=22.18`.
82
+
83
+ ## Versioning
84
+
85
+ This package follows strict semver: **any change that can make previously passing code fail is a major release**. That includes a new error-level rule, a rule moving from warning to error, and a newer required tool or Node version. Projects can stay on an older major for as long as they want.
package/package.json CHANGED
@@ -1,6 +1,172 @@
1
1
  {
2
2
  "name": "@santux/eslint-config",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0",
4
+ "description": "Shared ESLint, Prettier and commitlint config for TypeScript projects.",
5
+ "keywords": [
6
+ "commitlint",
7
+ "eslint",
8
+ "eslint-config",
9
+ "prettier"
10
+ ],
11
+ "license": "ISC",
12
+ "author": {
13
+ "name": "Nicolás Santuccio",
14
+ "email": "nico.santuccio@gmail.com"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/nicosantux/santux.git",
19
+ "directory": "packages/eslint-config"
20
+ },
21
+ "files": [
22
+ "src",
23
+ "!src/test",
24
+ "README.md"
25
+ ],
26
+ "type": "module",
27
+ "exports": {
28
+ "./lint": {
29
+ "types": "./src/lint.d.ts",
30
+ "default": "./src/lint.js"
31
+ },
32
+ "./format": {
33
+ "types": "./src/format.d.ts",
34
+ "default": "./src/format.js"
35
+ },
36
+ "./format-astro": {
37
+ "types": "./src/format-astro.d.ts",
38
+ "default": "./src/format-astro.js"
39
+ },
40
+ "./format-tailwind": {
41
+ "types": "./src/format-tailwind.d.ts",
42
+ "default": "./src/format-tailwind.js"
43
+ },
44
+ "./commitlint": {
45
+ "types": "./src/commitlint.d.ts",
46
+ "default": "./src/commitlint.js"
47
+ },
48
+ "./astro": {
49
+ "types": "./src/astro.d.ts",
50
+ "default": "./src/astro.js"
51
+ },
52
+ "./astro-react": {
53
+ "types": "./src/astro-react.d.ts",
54
+ "default": "./src/astro-react.js"
55
+ },
56
+ "./next": {
57
+ "types": "./src/next.d.ts",
58
+ "default": "./src/next.js"
59
+ },
60
+ "./react-vite": {
61
+ "types": "./src/react-vite.d.ts",
62
+ "default": "./src/react-vite.js"
63
+ },
64
+ "./tanstack-start": {
65
+ "types": "./src/tanstack-start.d.ts",
66
+ "default": "./src/tanstack-start.js"
67
+ },
68
+ "./tailwind": {
69
+ "types": "./src/tailwind.d.ts",
70
+ "default": "./src/tailwind.js"
71
+ }
72
+ },
73
+ "publishConfig": {
74
+ "access": "public"
75
+ },
76
+ "scripts": {
77
+ "test": "vitest run",
78
+ "typecheck": "tsc --noEmit"
79
+ },
80
+ "devDependencies": {
81
+ "@commitlint/cli": "21.2.4",
82
+ "@commitlint/config-conventional": "21.2.4",
83
+ "@eslint/js": "9.39.5",
84
+ "@next/eslint-plugin-next": "16.4.6",
85
+ "@shadcn/lint": "0.2.5",
86
+ "@stylistic/eslint-plugin": "5.10.0",
87
+ "eslint": "9.39.5",
88
+ "eslint-config-prettier": "10.1.8",
89
+ "eslint-import-resolver-typescript": "4.4.5",
90
+ "eslint-plugin-astro": "1.7.0",
91
+ "eslint-plugin-check-file": "3.3.2",
92
+ "eslint-plugin-import-x": "4.18.1",
93
+ "eslint-plugin-jsx-a11y": "6.10.2",
94
+ "eslint-plugin-react": "7.37.5",
95
+ "eslint-plugin-react-hooks": "7.1.1",
96
+ "eslint-plugin-react-refresh": "0.5.7",
97
+ "eslint-plugin-unicorn": "65.0.1",
98
+ "globals": "17.14.0",
99
+ "husky": "9.1.7",
100
+ "prettier": "3.10.1",
101
+ "prettier-plugin-astro": "0.14.1",
102
+ "prettier-plugin-tailwindcss": "0.8.1",
103
+ "tailwindcss": "4.3.5",
104
+ "typescript": "6.0.3",
105
+ "typescript-eslint": "8.72.2",
106
+ "vitest": "5.1.0"
107
+ },
108
+ "peerDependencies": {
109
+ "@commitlint/cli": "21.2.4",
110
+ "@commitlint/config-conventional": "21.2.4",
111
+ "@eslint/js": "9.39.5",
112
+ "@next/eslint-plugin-next": "16.4.6",
113
+ "@shadcn/lint": "0.2.5",
114
+ "@stylistic/eslint-plugin": "5.10.0",
115
+ "@typescript/typescript6": "6.0.2",
116
+ "eslint": "9.39.5",
117
+ "eslint-config-prettier": "10.1.8",
118
+ "eslint-import-resolver-typescript": "4.4.5",
119
+ "eslint-plugin-astro": "1.7.0",
120
+ "eslint-plugin-check-file": "3.3.2",
121
+ "eslint-plugin-import-x": "4.18.1",
122
+ "eslint-plugin-jsx-a11y": "6.10.2",
123
+ "eslint-plugin-react": "7.37.5",
124
+ "eslint-plugin-react-hooks": "7.1.1",
125
+ "eslint-plugin-react-refresh": "0.5.7",
126
+ "eslint-plugin-unicorn": "65.0.1",
127
+ "globals": "17.14.0",
128
+ "husky": "9.1.7",
129
+ "prettier": "3.10.1",
130
+ "prettier-plugin-astro": "0.14.1",
131
+ "prettier-plugin-tailwindcss": "0.8.1",
132
+ "typescript-eslint": "8.72.2"
133
+ },
134
+ "peerDependenciesMeta": {
135
+ "@next/eslint-plugin-next": {
136
+ "optional": true
137
+ },
138
+ "@shadcn/lint": {
139
+ "optional": true
140
+ },
141
+ "@typescript/typescript6": {
142
+ "optional": true
143
+ },
144
+ "eslint-plugin-astro": {
145
+ "optional": true
146
+ },
147
+ "eslint-plugin-jsx-a11y": {
148
+ "optional": true
149
+ },
150
+ "eslint-plugin-react": {
151
+ "optional": true
152
+ },
153
+ "eslint-plugin-react-hooks": {
154
+ "optional": true
155
+ },
156
+ "eslint-plugin-react-refresh": {
157
+ "optional": true
158
+ },
159
+ "globals": {
160
+ "optional": true
161
+ },
162
+ "prettier-plugin-astro": {
163
+ "optional": true
164
+ },
165
+ "prettier-plugin-tailwindcss": {
166
+ "optional": true
167
+ }
168
+ },
169
+ "engines": {
170
+ "node": ">=22.18"
171
+ }
172
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import globals from 'globals'
3
+
4
+ import { reactConfig } from './react.js'
5
+
6
+ export default defineConfig([{ ...reactConfig(globals.browser), files: ['**/*.{jsx,tsx}'] }])
package/src/astro.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
package/src/astro.js ADDED
@@ -0,0 +1,68 @@
1
+ import { createRequire } from 'node:module'
2
+
3
+ import astroPlugin from 'eslint-plugin-astro'
4
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'
5
+ import { defineConfig, globalIgnores } from 'eslint/config'
6
+ import globals from 'globals'
7
+ import tsEslint from 'typescript-eslint'
8
+
9
+ // The rules of eslint-plugin-astro's `jsx-a11y-recommended` config, limited to .astro files. The
10
+ // config itself registers its own copy of the `jsx-a11y` plugin, which clashes with the React rules
11
+ const a11yRules = Object.fromEntries(
12
+ Object.entries(jsxA11yPlugin.flatConfigs.recommended.rules).map(([name, entry]) => [
13
+ `astro/${name}`,
14
+ entry,
15
+ ]),
16
+ )
17
+
18
+ // import-x parses the modules an .astro file imports with the .astro parser unless told otherwise
19
+ const tsParserPath = createRequire(import.meta.resolve('typescript-eslint')).resolve(
20
+ '@typescript-eslint/parser',
21
+ )
22
+
23
+ export default defineConfig([
24
+ globalIgnores(['**/.astro/**']),
25
+ astroPlugin.configs['flat/recommended'],
26
+ {
27
+ files: ['**/*.astro'],
28
+ // eslint-plugin-astro only picks the TS parser and the TS <script> processor when it finds the
29
+ // parser hoisted to the project root, and astro-eslint-parser does not support `projectService`
30
+ processor: 'astro/client-side-ts',
31
+ languageOptions: {
32
+ parserOptions: {
33
+ parser: tsEslint.parser,
34
+ extraFileExtensions: ['.astro'],
35
+ project: true,
36
+ projectService: false,
37
+ },
38
+ },
39
+ settings: {
40
+ 'import-x/parsers': {
41
+ [tsParserPath]: ['.js', '.cjs', '.mjs', '.jsx', '.ts', '.cts', '.mts', '.tsx'],
42
+ },
43
+ },
44
+ rules: {
45
+ ...a11yRules,
46
+ // Astro types a component's props through an interface named `Props`
47
+ '@typescript-eslint/naming-convention': [
48
+ 'error',
49
+ { format: ['PascalCase'], selector: ['typeLike', 'enumMember'] },
50
+ {
51
+ custom: { match: false, regex: '^I[A-Z]|^(Interface|State)$' },
52
+ format: ['PascalCase'],
53
+ selector: 'interface',
54
+ },
55
+ ],
56
+ },
57
+ },
58
+ // The <script> blocks are virtual files that no tsconfig includes, so they get no type information
59
+ { files: ['**/*.astro/*.ts'], ...tsEslint.configs.disableTypeChecked },
60
+ {
61
+ files: ['**/pages/**'],
62
+ rules: {
63
+ 'check-file/filename-naming-convention': 'off',
64
+ 'check-file/folder-naming-convention': 'off',
65
+ },
66
+ },
67
+ { languageOptions: { globals: { ...globals.browser, ...globals.node } } },
68
+ ])
@@ -0,0 +1,2 @@
1
+ declare const config: { extends: string[]; rules: Record<string, [number, string, unknown]> }
2
+ export default config
@@ -0,0 +1,8 @@
1
+ /** @type {import('@commitlint/types').UserConfig} */
2
+ export default {
3
+ extends: ['@commitlint/config-conventional'],
4
+ rules: {
5
+ 'subject-case': [2, 'always', 'lower-case'],
6
+ 'body-max-line-length': [2, 'always', 72],
7
+ },
8
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ declare const config: Config
4
+ export default config
@@ -0,0 +1,5 @@
1
+ /** @type {import('prettier').Config} */
2
+ export default {
3
+ plugins: ['prettier-plugin-astro'],
4
+ overrides: [{ files: '*.astro', options: { parser: 'astro' } }],
5
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ declare const config: Config
4
+ export default config
@@ -0,0 +1,4 @@
1
+ /** @type {import('prettier').Config} */
2
+ export default {
3
+ plugins: ['prettier-plugin-tailwindcss'],
4
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'prettier'
2
+
3
+ declare const config: Config
4
+ export default config
package/src/format.js ADDED
@@ -0,0 +1,15 @@
1
+ /** @type {import('prettier').Config} */
2
+ export default {
3
+ arrowParens: 'always',
4
+ bracketSameLine: false,
5
+ bracketSpacing: true,
6
+ endOfLine: 'auto',
7
+ jsxSingleQuote: true,
8
+ printWidth: 100,
9
+ quoteProps: 'as-needed',
10
+ semi: false,
11
+ singleQuote: true,
12
+ tabWidth: 2,
13
+ trailingComma: 'all',
14
+ useTabs: false,
15
+ }
@@ -0,0 +1 @@
1
+ export declare function importOrder(earlyPackages: string[]): [string, object]
@@ -0,0 +1,17 @@
1
+ /** `import-x/order` options. Packages in `earlyPackages` are grouped before the other builtins. */
2
+ export function importOrder(earlyPackages) {
3
+ return [
4
+ 'warn',
5
+ {
6
+ alphabetize: { order: 'asc', caseInsensitive: true },
7
+ groups: ['type', 'builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
8
+ 'newlines-between': 'always',
9
+ pathGroups: [
10
+ { pattern: '@/**', group: 'internal', position: 'after' },
11
+ { pattern: `{${earlyPackages.join(',')}}`, group: 'builtin', position: 'before' },
12
+ ],
13
+ pathGroupsExcludedImportTypes: ['builtin', 'internal'],
14
+ warnOnUnassignedImports: true,
15
+ },
16
+ ]
17
+ }
package/src/lint.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
package/src/lint.js ADDED
@@ -0,0 +1,169 @@
1
+ import eslint from '@eslint/js'
2
+ import stylistic from '@stylistic/eslint-plugin'
3
+ import eslintConfigPrettier from 'eslint-config-prettier/flat'
4
+ import { createTypeScriptImportResolver } from 'eslint-import-resolver-typescript'
5
+ import checkFilePlugin from 'eslint-plugin-check-file'
6
+ import { importX } from 'eslint-plugin-import-x'
7
+ import unicornPlugin from 'eslint-plugin-unicorn'
8
+ import { defineConfig, globalIgnores } from 'eslint/config'
9
+ import tsEslint from 'typescript-eslint'
10
+
11
+ import { importOrder } from './import-order.js'
12
+
13
+ export default defineConfig([
14
+ globalIgnores(['**/node_modules', '**/build', '**/dist']),
15
+ // Vanilla ESLint
16
+ {
17
+ extends: [eslint.configs.recommended],
18
+ rules: {
19
+ eqeqeq: ['error', 'always', { null: 'ignore' }],
20
+ 'no-await-in-loop': 'error',
21
+ 'no-console': 'error',
22
+ 'no-else-return': 'warn',
23
+ 'no-implicit-coercion': 'error',
24
+ 'no-param-reassign': 'error',
25
+ 'no-self-compare': 'error',
26
+ 'no-undef-init': 'warn',
27
+ 'no-unneeded-ternary': 'error',
28
+ 'no-var': 'error',
29
+ 'object-shorthand': 'warn',
30
+ 'prefer-object-spread': 'warn',
31
+ 'prefer-rest-params': 'error',
32
+ 'prefer-spread': 'error',
33
+ 'prefer-template': 'warn',
34
+ },
35
+ },
36
+ // Check files
37
+ {
38
+ plugins: { 'check-file': checkFilePlugin },
39
+ rules: {
40
+ 'check-file/filename-naming-convention': [
41
+ 'error',
42
+ { '**/*.{js,jsx,ts,tsx}': 'KEBAB_CASE' },
43
+ { ignoreMiddleExtensions: true },
44
+ ],
45
+ 'check-file/folder-naming-convention': ['error', { '**/src/**/': 'KEBAB_CASE' }],
46
+ },
47
+ },
48
+ // Import order
49
+ {
50
+ plugins: { 'import-x': importX },
51
+ settings: {
52
+ 'import-x/extensions': ['.js', '.jsx', '.ts', '.tsx'],
53
+ 'import-x/resolver-next': [createTypeScriptImportResolver()],
54
+ },
55
+ rules: {
56
+ 'import-x/first': 'error',
57
+ 'import-x/newline-after-import': 'warn',
58
+ 'import-x/no-duplicates': ['warn', { 'prefer-inline': true }],
59
+ 'import-x/no-cycle': 'error',
60
+ 'import-x/no-extraneous-dependencies': ['error', { includeTypes: true }],
61
+ 'import-x/no-mutable-exports': 'error',
62
+ 'import-x/no-self-import': 'error',
63
+ 'import-x/no-useless-path-segments': 'error',
64
+ 'import-x/order': importOrder(['react', 'react-dom', 'react-dom/**']),
65
+ },
66
+ },
67
+ // Stylistic
68
+ {
69
+ plugins: { '@stylistic': stylistic },
70
+ rules: {
71
+ '@stylistic/padding-line-between-statements': [
72
+ 'warn',
73
+ { blankLine: 'always', prev: 'directive', next: '*' },
74
+ { blankLine: 'always', prev: '*', next: 'block-like' },
75
+ { blankLine: 'always', prev: 'block-like', next: '*' },
76
+ { blankLine: 'always', prev: '*', next: 'return' },
77
+ { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
78
+ {
79
+ blankLine: 'any',
80
+ prev: ['singleline-const', 'singleline-let', 'singleline-var'],
81
+ next: ['singleline-const', 'singleline-let', 'singleline-var'],
82
+ },
83
+ ],
84
+ },
85
+ },
86
+ // TypeScript
87
+ {
88
+ extends: [tsEslint.configs.strictTypeChecked, tsEslint.configs.stylisticTypeChecked],
89
+ languageOptions: { parserOptions: { projectService: true } },
90
+ rules: {
91
+ '@typescript-eslint/consistent-type-exports': [
92
+ 'warn',
93
+ { fixMixedExportsWithInlineTypeSpecifier: true },
94
+ ],
95
+ '@typescript-eslint/consistent-type-imports': ['warn', { fixStyle: 'inline-type-imports' }],
96
+ '@typescript-eslint/default-param-last': 'error',
97
+ '@typescript-eslint/member-ordering': [
98
+ 'error',
99
+ { interfaces: { order: 'natural-case-insensitive' } },
100
+ ],
101
+ '@typescript-eslint/method-signature-style': 'warn',
102
+ '@typescript-eslint/naming-convention': [
103
+ 'error',
104
+ { format: ['PascalCase'], selector: ['typeLike', 'enumMember'] },
105
+ {
106
+ custom: {
107
+ match: false,
108
+ regex: '^I[A-Z]|^(Interface|Props|State)$',
109
+ },
110
+ format: ['PascalCase'],
111
+ selector: 'interface',
112
+ },
113
+ ],
114
+ '@typescript-eslint/no-import-type-side-effects': 'warn',
115
+ '@typescript-eslint/no-confusing-void-expression': ['error', { ignoreArrowShorthand: true }],
116
+ '@typescript-eslint/no-loop-func': 'error',
117
+ '@typescript-eslint/no-redundant-type-constituents': 'warn',
118
+ '@typescript-eslint/no-unnecessary-qualifier': 'warn',
119
+ '@typescript-eslint/no-unused-vars': [
120
+ 'error',
121
+ {
122
+ args: 'all',
123
+ argsIgnorePattern: '^_',
124
+ caughtErrors: 'all',
125
+ caughtErrorsIgnorePattern: '^_',
126
+ destructuredArrayIgnorePattern: '^_',
127
+ varsIgnorePattern: '^_',
128
+ ignoreRestSiblings: true,
129
+ },
130
+ ],
131
+ '@typescript-eslint/prefer-nullish-coalescing': [
132
+ 'error',
133
+ {
134
+ ignorePrimitives: {
135
+ bigint: true,
136
+ boolean: true,
137
+ number: true,
138
+ string: true,
139
+ },
140
+ },
141
+ ],
142
+ '@typescript-eslint/require-array-sort-compare': 'error',
143
+ '@typescript-eslint/switch-exhaustiveness-check': 'error',
144
+ '@typescript-eslint/triple-slash-reference': 'off',
145
+ // After the typescript-eslint presets, which set it to error in TypeScript files
146
+ 'prefer-const': 'warn',
147
+ },
148
+ },
149
+ // Disable type checked in .js files
150
+ {
151
+ files: ['**/*.{js,cjs,mjs}'],
152
+ ...tsEslint.configs.disableTypeChecked,
153
+ },
154
+ // Unicorn
155
+ {
156
+ plugins: { unicorn: unicornPlugin },
157
+ rules: {
158
+ 'unicorn/error-message': 'error',
159
+ 'unicorn/no-empty-file': 'error',
160
+ 'unicorn/no-instanceof-builtins': 'error',
161
+ 'unicorn/no-useless-spread': 'warn',
162
+ 'unicorn/prefer-array-flat-map': 'warn',
163
+ 'unicorn/prefer-node-protocol': 'warn',
164
+ 'unicorn/throw-new-error': 'error',
165
+ },
166
+ },
167
+ // Prettier
168
+ eslintConfigPrettier,
169
+ ])
package/src/next.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
package/src/next.js ADDED
@@ -0,0 +1,18 @@
1
+ import nextPlugin from '@next/eslint-plugin-next'
2
+ import { defineConfig, globalIgnores } from 'eslint/config'
3
+ import globals from 'globals'
4
+
5
+ import { importOrder } from './import-order.js'
6
+ import { reactConfig } from './react.js'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['**/.next/**', '**/out/**', '**/next-env.d.ts']),
10
+ {
11
+ rules: {
12
+ 'check-file/folder-naming-convention': ['error', { '**/src/**/': 'NEXT_JS_APP_ROUTER_CASE' }],
13
+ 'import-x/order': importOrder(['next', 'next/**', 'react', 'react-dom', 'react-dom/**']),
14
+ },
15
+ },
16
+ nextPlugin.configs['core-web-vitals'],
17
+ reactConfig({ ...globals.browser, ...globals.node }),
18
+ ])
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,15 @@
1
+ import reactRefreshPlugin from 'eslint-plugin-react-refresh'
2
+ import { defineConfig } from 'eslint/config'
3
+ import globals from 'globals'
4
+
5
+ import { importOrder } from './import-order.js'
6
+ import { reactConfig } from './react.js'
7
+
8
+ export default defineConfig([
9
+ {
10
+ rules: {
11
+ 'import-x/order': importOrder(['react', 'react-dom', 'react-dom/**']),
12
+ },
13
+ },
14
+ reactConfig(globals.browser, [reactRefreshPlugin.configs.vite]),
15
+ ])
package/src/react.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ export declare function reactConfig(
4
+ globals: Record<string, boolean | 'readonly' | 'writable' | 'off'>,
5
+ extraExtends?: Config[],
6
+ ): Config
package/src/react.js ADDED
@@ -0,0 +1,37 @@
1
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y'
2
+ import reactPlugin from 'eslint-plugin-react'
3
+ import reactHooksPlugin from 'eslint-plugin-react-hooks'
4
+
5
+ /** React config block shared by the Framework entries. `extraExtends` go after the React presets. */
6
+ export function reactConfig(globals, extraExtends = []) {
7
+ return {
8
+ files: ['**/*.{js,mjs,cjs,jsx,ts,tsx}'],
9
+ extends: [
10
+ reactPlugin.configs.flat.recommended,
11
+ reactPlugin.configs.flat['jsx-runtime'],
12
+ reactHooksPlugin.configs.flat['recommended-latest'],
13
+ jsxA11yPlugin.flatConfigs.recommended,
14
+ ...extraExtends,
15
+ ],
16
+ languageOptions: {
17
+ globals,
18
+ parserOptions: { ecmaFeatures: { jsx: true } },
19
+ },
20
+ rules: {
21
+ 'react/function-component-definition': 'warn',
22
+ 'react/hook-use-state': 'error',
23
+ 'react/jsx-boolean-value': 'warn',
24
+ 'react/jsx-curly-brace-presence': 'warn',
25
+ 'react/jsx-fragments': 'warn',
26
+ 'react/jsx-no-leaked-render': 'warn',
27
+ 'react/jsx-no-target-blank': ['error', { allowReferrer: true }],
28
+ 'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
29
+ 'react/jsx-sort-props': ['warn', { noSortAlphabetically: false }],
30
+ 'react/no-array-index-key': 'warn',
31
+ 'react/no-unstable-nested-components': 'error',
32
+ 'react/prop-types': 'off',
33
+ 'react/self-closing-comp': 'warn',
34
+ },
35
+ settings: { react: { version: '19' } },
36
+ }
37
+ }
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,16 @@
1
+ import { plugin as shadcnPlugin } from '@shadcn/lint'
2
+ import { defineConfig } from 'eslint/config'
3
+
4
+ export default defineConfig([
5
+ {
6
+ files: ['**/*.{js,mjs,cjs,jsx,ts,tsx}'],
7
+ plugins: { shadcn: shadcnPlugin },
8
+ rules: {
9
+ 'shadcn/no-arbitrary-values': 'error',
10
+ 'shadcn/no-inline-styles': 'error',
11
+ 'shadcn/no-raw-colors': 'error',
12
+ 'shadcn/no-restyle': 'error',
13
+ 'shadcn/no-unknown-classes': 'error',
14
+ },
15
+ },
16
+ ])
@@ -0,0 +1,4 @@
1
+ import type { Config } from 'eslint/config'
2
+
3
+ declare const config: Config[]
4
+ export default config
@@ -0,0 +1,22 @@
1
+ import { defineConfig, globalIgnores } from 'eslint/config'
2
+ import globals from 'globals'
3
+
4
+ import { importOrder } from './import-order.js'
5
+ import { reactConfig } from './react.js'
6
+
7
+ export default defineConfig([
8
+ globalIgnores(['**/.output/**', '**/.tanstack/**', '**/routeTree.gen.ts']),
9
+ {
10
+ rules: {
11
+ 'import-x/order': importOrder(['react', 'react-dom', 'react-dom/**']),
12
+ },
13
+ },
14
+ {
15
+ files: ['**/routes/**'],
16
+ rules: {
17
+ 'check-file/filename-naming-convention': 'off',
18
+ 'check-file/folder-naming-convention': 'off',
19
+ },
20
+ },
21
+ reactConfig({ ...globals.browser, ...globals.node }),
22
+ ])