@docyrus/rules 0.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 ADDED
@@ -0,0 +1,218 @@
1
+ # @docyrus/rules
2
+
3
+ Docyrus projeleri için merkezi ESLint ve Biome konfigürasyonları.
4
+
5
+ ## Kurulum
6
+
7
+ ```bash
8
+ pnpm add -D @docyrus/rules eslint typescript
9
+ ```
10
+
11
+ Biome kullanıyorsanız:
12
+
13
+ ```bash
14
+ pnpm add -D @docyrus/rules @biomejs/biome
15
+ ```
16
+
17
+ ---
18
+
19
+ ## ESLint
20
+
21
+ Paket **kompozisyon** yaklaşımı kullanır. Her config bağımsız bir katmandır — projenize uygun olanları birleştirirsiniz.
22
+
23
+ ### Config'ler
24
+
25
+ | Config | İçerik |
26
+ |--------|--------|
27
+ | `baseConfig` | TypeScript parser, @stylistic, import sıralaması, unused imports, genel kurallar |
28
+ | `reactConfig` | React, React Hooks kuralları |
29
+ | `nextjsConfig` | Next.js recommended + core-web-vitals, React Refresh |
30
+ | `expoConfig` | Expo, React Native kuralları |
31
+ | `aiConfig` | Vercel AI SDK güvenlik kuralları (OWASP Agentic Top 10) |
32
+
33
+ ### Kullanım
34
+
35
+ Tüm config'ler tek entry point'ten import edilir:
36
+
37
+ ```js
38
+ import { baseConfig, reactConfig, nextjsConfig, expoConfig, aiConfig } from '@docyrus/rules/eslint';
39
+ ```
40
+
41
+ Ya da tek tek:
42
+
43
+ ```js
44
+ import { baseConfig } from '@docyrus/rules/eslint/base';
45
+ import { reactConfig } from '@docyrus/rules/eslint/react';
46
+ ```
47
+
48
+ ---
49
+
50
+ ### React Projesi
51
+
52
+ ```js
53
+ // eslint.config.mjs
54
+ import { baseConfig, reactConfig } from '@docyrus/rules/eslint';
55
+
56
+ export default [...baseConfig, ...reactConfig];
57
+ ```
58
+
59
+ ### Next.js Projesi
60
+
61
+ ```js
62
+ // eslint.config.mjs
63
+ import { baseConfig, reactConfig, nextjsConfig } from '@docyrus/rules/eslint';
64
+
65
+ export default [...baseConfig, ...reactConfig, ...nextjsConfig];
66
+ ```
67
+
68
+ ### Next.js + AI Projesi
69
+
70
+ ```js
71
+ // eslint.config.mjs
72
+ import { baseConfig, reactConfig, nextjsConfig, aiConfig } from '@docyrus/rules/eslint';
73
+
74
+ export default [...baseConfig, ...reactConfig, ...nextjsConfig, ...aiConfig];
75
+ ```
76
+
77
+ ### Expo / React Native Projesi
78
+
79
+ ```js
80
+ // eslint.config.mjs
81
+ import { baseConfig, reactConfig, expoConfig } from '@docyrus/rules/eslint';
82
+
83
+ export default [
84
+ ...baseConfig,
85
+ ...reactConfig,
86
+ ...expoConfig,
87
+ {
88
+ settings: {
89
+ 'import-x/resolver': {
90
+ typescript: {
91
+ project: ['apps/*/tsconfig.json', 'packages/*/tsconfig.json']
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ];
97
+ ```
98
+
99
+ ### Sade TypeScript Projesi
100
+
101
+ ```js
102
+ // eslint.config.mjs
103
+ import { baseConfig } from '@docyrus/rules/eslint';
104
+
105
+ export default [...baseConfig];
106
+ ```
107
+
108
+ ### Proje-Özel Override
109
+
110
+ Her config'in üstüne kendi kurallarınızı ekleyebilirsiniz:
111
+
112
+ ```js
113
+ // eslint.config.mjs
114
+ import { baseConfig, reactConfig } from '@docyrus/rules/eslint';
115
+
116
+ export default [
117
+ ...baseConfig,
118
+ ...reactConfig,
119
+ {
120
+ rules: {
121
+ 'no-console': 'off',
122
+ 'react/no-array-index-key': 'off'
123
+ }
124
+ }
125
+ ];
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Biome
131
+
132
+ Biome config'leri `extends` ile kullanılır. Projenize uygun preset'i seçin.
133
+
134
+ ### Config'ler
135
+
136
+ | Config | İçerik |
137
+ |--------|--------|
138
+ | `@docyrus/rules/biome/base` | Space indent, tek tırnak, recommended kurallar |
139
+ | `@docyrus/rules/biome/cloudflare` | Tab indent, strict kurallar (MCP/Workers projeleri) |
140
+ | `@docyrus/rules/biome/react` | Base + React ignore'ları |
141
+ | `@docyrus/rules/biome/nextjs` | Base + Next.js ignore'ları (.next, .open-next) |
142
+
143
+ ### Kullanım
144
+
145
+ ```json
146
+ // biome.json
147
+ {
148
+ "extends": ["@docyrus/rules/biome/base"]
149
+ }
150
+ ```
151
+
152
+ ### Cloudflare Workers / MCP Projesi
153
+
154
+ ```json
155
+ // biome.json
156
+ {
157
+ "extends": ["@docyrus/rules/biome/cloudflare"]
158
+ }
159
+ ```
160
+
161
+ ### Next.js Projesi (Biome)
162
+
163
+ ```json
164
+ // biome.json
165
+ {
166
+ "extends": ["@docyrus/rules/biome/nextjs"]
167
+ }
168
+ ```
169
+
170
+ ### Override
171
+
172
+ ```json
173
+ // biome.json
174
+ {
175
+ "extends": ["@docyrus/rules/biome/base"],
176
+ "linter": {
177
+ "rules": {
178
+ "suspicious": {
179
+ "noExplicitAny": "off"
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Hangi Config Hangi Proje İçin?
189
+
190
+ | Proje Tipi | ESLint Config'leri | Biome Config |
191
+ |------------|-------------------|--------------|
192
+ | React (Vite, CRA) | `baseConfig` + `reactConfig` | `biome/react` |
193
+ | Next.js | `baseConfig` + `reactConfig` + `nextjsConfig` | `biome/nextjs` |
194
+ | Next.js + AI | `baseConfig` + `reactConfig` + `nextjsConfig` + `aiConfig` | `biome/nextjs` |
195
+ | Expo / React Native | `baseConfig` + `reactConfig` + `expoConfig` | — |
196
+ | Node.js / TypeScript | `baseConfig` | `biome/base` |
197
+ | Cloudflare Workers / MCP | — | `biome/cloudflare` |
198
+
199
+ ---
200
+
201
+ ## Dahil Olan Plugin'ler
202
+
203
+ ESLint config'leri tüm gerekli plugin'leri dependency olarak getirir. Ayrıca kurmanıza gerek yok.
204
+
205
+ | Plugin | Config |
206
+ |--------|--------|
207
+ | `@stylistic/eslint-plugin` | base |
208
+ | `@typescript-eslint/eslint-plugin` | base |
209
+ | `@typescript-eslint/parser` | base |
210
+ | `eslint-plugin-import-x` | base |
211
+ | `eslint-plugin-unused-imports` | base |
212
+ | `eslint-plugin-react` | react |
213
+ | `eslint-plugin-react-hooks` | react |
214
+ | `@next/eslint-plugin-next` | nextjs |
215
+ | `eslint-plugin-react-refresh` | nextjs |
216
+ | `eslint-config-expo` | expo |
217
+ | `eslint-plugin-react-native` | expo |
218
+ | `eslint-plugin-vercel-ai-security` | ai |
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
3
+ "root": true,
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": true
8
+ },
9
+ "files": {
10
+ "ignore": [ "dist", "node_modules", "build", "coverage", ".history" ]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "indentWidth": 2,
16
+ "lineWidth": 100
17
+ },
18
+ "javascript": {
19
+ "formatter": {
20
+ "quoteStyle": "single",
21
+ "semicolons": "always",
22
+ "trailingCommas": "es5"
23
+ }
24
+ },
25
+ "organizeImports": {
26
+ "enabled": true
27
+ },
28
+ "linter": {
29
+ "enabled": true,
30
+ "rules": {
31
+ "recommended": true
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,69 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
3
+ "root": true,
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": true
8
+ },
9
+ "files": {
10
+ "includes": [ "src/**/*" ]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "tab",
15
+ "indentWidth": 2,
16
+ "lineWidth": 100
17
+ },
18
+ "javascript": {
19
+ "formatter": {
20
+ "quoteStyle": "single",
21
+ "semicolons": "always",
22
+ "trailingCommas": "es5"
23
+ }
24
+ },
25
+ "organizeImports": {
26
+ "enabled": true
27
+ },
28
+ "assist": {
29
+ "enabled": true,
30
+ "actions": {
31
+ "source": {
32
+ "useSortedKeys": "off"
33
+ }
34
+ }
35
+ },
36
+ "linter": {
37
+ "enabled": true,
38
+ "rules": {
39
+ "recommended": true,
40
+ "correctness": {
41
+ "noUnusedVariables": "error",
42
+ "noUnusedImports": "error",
43
+ "noUnreachable": "error",
44
+ "noVoidTypeReturn": "error"
45
+ },
46
+ "nursery": {
47
+ "noDeprecatedImports": "error"
48
+ },
49
+ "style": {
50
+ "noInferrableTypes": "error",
51
+ "noNonNullAssertion": "off",
52
+ "noParameterAssign": "error",
53
+ "noUnusedTemplateLiteral": "error",
54
+ "noUselessElse": "error",
55
+ "useAsConstAssertion": "error",
56
+ "useDefaultParameterLast": "error",
57
+ "useEnumInitializers": "error",
58
+ "useNumberNamespace": "error",
59
+ "useSelfClosingElements": "error",
60
+ "useSingleVarDeclarator": "error"
61
+ },
62
+ "suspicious": {
63
+ "noConfusingVoidType": "off",
64
+ "noDebugger": "off",
65
+ "noExplicitAny": "off"
66
+ }
67
+ }
68
+ }
69
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
3
+ "root": true,
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": true
8
+ },
9
+ "files": {
10
+ "ignore": [ "dist", "node_modules", "build", "coverage", ".history", ".next", ".open-next" ]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "indentWidth": 2,
16
+ "lineWidth": 100
17
+ },
18
+ "javascript": {
19
+ "formatter": {
20
+ "quoteStyle": "single",
21
+ "semicolons": "always",
22
+ "trailingCommas": "es5"
23
+ }
24
+ },
25
+ "organizeImports": {
26
+ "enabled": true
27
+ },
28
+ "linter": {
29
+ "enabled": true,
30
+ "rules": {
31
+ "recommended": true,
32
+ "correctness": {
33
+ "noUnusedVariables": "error",
34
+ "noUnusedImports": "error"
35
+ },
36
+ "style": {
37
+ "useSelfClosingElements": "error"
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,41 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
3
+ "root": true,
4
+ "vcs": {
5
+ "enabled": true,
6
+ "clientKind": "git",
7
+ "useIgnoreFile": true
8
+ },
9
+ "files": {
10
+ "ignore": [ "dist", "node_modules", "build", "coverage", ".history" ]
11
+ },
12
+ "formatter": {
13
+ "enabled": true,
14
+ "indentStyle": "space",
15
+ "indentWidth": 2,
16
+ "lineWidth": 100
17
+ },
18
+ "javascript": {
19
+ "formatter": {
20
+ "quoteStyle": "single",
21
+ "semicolons": "always",
22
+ "trailingCommas": "es5"
23
+ }
24
+ },
25
+ "organizeImports": {
26
+ "enabled": true
27
+ },
28
+ "linter": {
29
+ "enabled": true,
30
+ "rules": {
31
+ "recommended": true,
32
+ "correctness": {
33
+ "noUnusedVariables": "error",
34
+ "noUnusedImports": "error"
35
+ },
36
+ "style": {
37
+ "useSelfClosingElements": "error"
38
+ }
39
+ }
40
+ }
41
+ }
@@ -0,0 +1,3 @@
1
+ import type { Linter } from 'eslint';
2
+ export declare const aiConfig: Linter.Config[];
3
+ //# sourceMappingURL=ai.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../../src/eslint/ai.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE7C,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,EAWnC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import vercelAiPlugin from 'eslint-plugin-vercel-ai-security';
2
+ export const aiConfig = [
3
+ {
4
+ files: ['**/*.{ts,tsx,js,jsx}'],
5
+ plugins: {
6
+ 'vercel-ai-security': vercelAiPlugin.plugin,
7
+ },
8
+ rules: {
9
+ // ─── Vercel AI Security (recommended) ───
10
+ ...vercelAiPlugin.configs.recommended.rules,
11
+ },
12
+ },
13
+ ];
@@ -0,0 +1,3 @@
1
+ import type { Linter } from 'eslint';
2
+ export declare const baseConfig: Linter.Config[];
3
+ //# sourceMappingURL=base.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.d.ts","sourceRoot":"","sources":["../../src/eslint/base.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAc7C,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAuRrC,CAAC"}
@@ -0,0 +1,291 @@
1
+ import eslintParser from '@typescript-eslint/parser';
2
+ import typescriptPlugin from '@typescript-eslint/eslint-plugin';
3
+ import stylisticPlugin from '@stylistic/eslint-plugin';
4
+ import unusedPlugin from 'eslint-plugin-unused-imports';
5
+ import importPlugin from 'eslint-plugin-import-x';
6
+ const customized = stylisticPlugin.configs.customize({
7
+ indent: 2,
8
+ quotes: 'single',
9
+ semi: true,
10
+ jsx: true,
11
+ arrowParens: true,
12
+ braceStyle: '1tbs',
13
+ blockSpacing: true,
14
+ quoteProps: 'as-needed',
15
+ commaDangle: 'always-multiline',
16
+ });
17
+ export const baseConfig = [
18
+ {
19
+ ignores: [
20
+ '**/dist/**',
21
+ '**/node_modules/**',
22
+ '**/build/**',
23
+ '**/coverage/**',
24
+ '**/.history/**',
25
+ '**/.turbo/**',
26
+ '**/scripts/**',
27
+ ],
28
+ },
29
+ {
30
+ files: ['**/*.{ts,tsx,mjs,js,jsx}'],
31
+ languageOptions: {
32
+ parser: eslintParser,
33
+ parserOptions: {
34
+ ecmaVersion: 'latest',
35
+ sourceType: 'module',
36
+ ecmaFeatures: {
37
+ jsx: true,
38
+ },
39
+ },
40
+ globals: {
41
+ __DEV__: 'readonly',
42
+ },
43
+ },
44
+ plugins: {
45
+ '@stylistic': stylisticPlugin,
46
+ '@typescript-eslint': typescriptPlugin,
47
+ 'unused-imports': unusedPlugin,
48
+ 'import-x': importPlugin,
49
+ },
50
+ settings: {
51
+ 'import-x/extensions': [
52
+ '.js',
53
+ '.jsx',
54
+ '.ts',
55
+ '.tsx',
56
+ '.mjs',
57
+ '.mts',
58
+ '.cts',
59
+ '.cjs',
60
+ ],
61
+ 'import-x/resolver': {
62
+ node: {
63
+ extensions: [
64
+ '.js',
65
+ '.jsx',
66
+ '.ts',
67
+ '.tsx',
68
+ '.mjs',
69
+ '.mts',
70
+ '.cts',
71
+ '.cjs',
72
+ ],
73
+ },
74
+ },
75
+ },
76
+ rules: {
77
+ // ─── Stylistic (base from customize) ───
78
+ ...customized.rules,
79
+ // ─── Stylistic overrides ───
80
+ '@stylistic/indent': [
81
+ 'error',
82
+ 2,
83
+ {
84
+ SwitchCase: 1,
85
+ VariableDeclarator: 'first',
86
+ FunctionDeclaration: { parameters: 'first' },
87
+ FunctionExpression: { parameters: 'first' },
88
+ CallExpression: { arguments: 'first' },
89
+ StaticBlock: { body: 1 },
90
+ MemberExpression: 1,
91
+ outerIIFEBody: 0,
92
+ ArrayExpression: 1,
93
+ ObjectExpression: 1,
94
+ offsetTernaryExpressions: true,
95
+ },
96
+ ],
97
+ '@stylistic/comma-dangle': ['error', 'never'],
98
+ '@stylistic/lines-between-class-members': ['error', 'always', { exceptAfterSingleLine: false }],
99
+ '@stylistic/jsx-closing-bracket-location': ['error', 'after-props'],
100
+ '@stylistic/jsx-indent-props': ['error', 2],
101
+ '@stylistic/jsx-quotes': ['error', 'prefer-double'],
102
+ '@stylistic/jsx-closing-tag-location': 'error',
103
+ '@stylistic/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
104
+ '@stylistic/jsx-curly-spacing': ['error', { when: 'never', children: true }],
105
+ '@stylistic/jsx-equals-spacing': ['error', 'never'],
106
+ '@stylistic/jsx-first-prop-new-line': ['error', 'multiline-multiprop'],
107
+ '@stylistic/jsx-max-props-per-line': ['error', { maximum: 1, when: 'multiline' }],
108
+ '@stylistic/jsx-newline': 'off',
109
+ '@stylistic/jsx-one-expression-per-line': 'off',
110
+ '@stylistic/jsx-self-closing-comp': ['error', { component: true, html: true }],
111
+ '@stylistic/jsx-sort-props': 'off',
112
+ '@stylistic/jsx-tag-spacing': [
113
+ 'error',
114
+ {
115
+ closingSlash: 'never',
116
+ beforeSelfClosing: 'always',
117
+ afterOpening: 'never',
118
+ beforeClosing: 'never',
119
+ },
120
+ ],
121
+ '@stylistic/jsx-wrap-multilines': 'off',
122
+ '@stylistic/jsx-curly-newline': ['error', { multiline: 'consistent', singleline: 'forbid' }],
123
+ '@stylistic/no-multi-spaces': [
124
+ 'error',
125
+ {
126
+ ignoreEOLComments: false,
127
+ includeTabs: true,
128
+ exceptions: {
129
+ Property: true,
130
+ BinaryExpression: true,
131
+ VariableDeclarator: false,
132
+ ImportDeclaration: false,
133
+ },
134
+ },
135
+ ],
136
+ '@stylistic/linebreak-style': ['error', 'unix'],
137
+ '@stylistic/max-len': [
138
+ 'error',
139
+ {
140
+ code: 200,
141
+ tabWidth: 2,
142
+ ignoreUrls: true,
143
+ ignoreComments: false,
144
+ ignoreRegExpLiterals: true,
145
+ ignoreStrings: true,
146
+ ignoreTemplateLiterals: true,
147
+ },
148
+ ],
149
+ '@stylistic/member-delimiter-style': [
150
+ 'error',
151
+ {
152
+ multiline: { delimiter: 'semi', requireLast: true },
153
+ singleline: { delimiter: 'semi', requireLast: false },
154
+ },
155
+ ],
156
+ '@stylistic/no-multiple-empty-lines': ['error', { max: 1, maxEOF: 0 }],
157
+ '@stylistic/no-trailing-spaces': 'error',
158
+ '@stylistic/padding-line-between-statements': [
159
+ 'error',
160
+ { blankLine: 'always', prev: '*', next: 'return' },
161
+ { blankLine: 'always', prev: ['const', 'let', 'var'], next: '*' },
162
+ { blankLine: 'any', prev: ['const', 'let', 'var'], next: ['const', 'let', 'var'] },
163
+ { blankLine: 'always', prev: 'directive', next: '*' },
164
+ { blankLine: 'any', prev: 'directive', next: 'directive' },
165
+ { blankLine: 'always', prev: ['case', 'default'], next: '*' },
166
+ ],
167
+ '@stylistic/array-bracket-newline': ['error', { multiline: true, minItems: 4 }],
168
+ '@stylistic/array-bracket-spacing': ['error', 'never', { singleValue: false, arraysInArrays: false, objectsInArrays: false }],
169
+ '@stylistic/array-element-newline': ['error', { consistent: false, multiline: true, minItems: 4 }],
170
+ '@stylistic/arrow-parens': ['error', 'as-needed', { requireForBlockBody: true }],
171
+ '@stylistic/block-spacing': 'error',
172
+ '@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
173
+ '@stylistic/comma-spacing': ['error', { before: false, after: true }],
174
+ '@stylistic/comma-style': ['error', 'last'],
175
+ '@stylistic/computed-property-spacing': ['error', 'never'],
176
+ '@stylistic/function-call-spacing': ['error', 'never'],
177
+ '@stylistic/function-paren-newline': ['error', 'multiline-arguments'],
178
+ '@stylistic/implicit-arrow-linebreak': ['error', 'beside'],
179
+ '@stylistic/quote-props': ['error', 'as-needed'],
180
+ '@stylistic/quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: 'always' }],
181
+ '@stylistic/semi': ['error', 'always'],
182
+ '@stylistic/semi-spacing': ['error', { before: false, after: true }],
183
+ '@stylistic/object-curly-newline': ['error', { consistent: true, minProperties: 4 }],
184
+ '@stylistic/object-curly-spacing': ['error', 'always', { objectsInObjects: true, arraysInObjects: true }],
185
+ '@stylistic/space-before-blocks': 'error',
186
+ '@stylistic/multiline-ternary': ['error', 'never'],
187
+ '@stylistic/multiline-comment-style': ['error', 'starred-block'],
188
+ '@stylistic/space-before-function-paren': [
189
+ 'error',
190
+ {
191
+ anonymous: 'always',
192
+ named: 'never',
193
+ asyncArrow: 'always',
194
+ },
195
+ ],
196
+ '@stylistic/space-in-parens': ['error', 'never'],
197
+ '@stylistic/space-infix-ops': 'error',
198
+ '@stylistic/space-unary-ops': ['error', { words: true, nonwords: false }],
199
+ '@stylistic/template-curly-spacing': 'error',
200
+ '@stylistic/arrow-spacing': ['error', { before: true, after: true }],
201
+ '@stylistic/type-annotation-spacing': 'error',
202
+ '@stylistic/eol-last': ['error', 'always'],
203
+ '@stylistic/key-spacing': ['error', { beforeColon: false }],
204
+ '@stylistic/jsx-pascal-case': 'error',
205
+ // ─── TypeScript rules ───
206
+ '@typescript-eslint/no-explicit-any': 'warn',
207
+ '@typescript-eslint/no-non-null-assertion': 'warn',
208
+ '@typescript-eslint/no-empty-interface': 'warn',
209
+ '@typescript-eslint/ban-ts-comment': 'warn',
210
+ '@typescript-eslint/consistent-type-imports': ['error', {
211
+ prefer: 'type-imports',
212
+ disallowTypeAnnotations: true,
213
+ }],
214
+ // ─── Unused imports ───
215
+ 'unused-imports/no-unused-imports': 'error',
216
+ 'unused-imports/no-unused-vars': [
217
+ 'error',
218
+ {
219
+ vars: 'all',
220
+ varsIgnorePattern: '^_',
221
+ args: 'after-used',
222
+ argsIgnorePattern: '^_',
223
+ caughtErrorsIgnorePattern: '^_',
224
+ },
225
+ ],
226
+ // ─── Import ordering ───
227
+ 'import-x/order': [
228
+ 'error',
229
+ {
230
+ groups: [
231
+ 'type',
232
+ 'builtin',
233
+ 'external',
234
+ 'internal',
235
+ ['sibling', 'parent'],
236
+ ],
237
+ 'newlines-between': 'always-and-inside-groups',
238
+ pathGroups: [
239
+ { pattern: 'react', group: 'type', position: 'before' },
240
+ { pattern: 'react', group: 'builtin', position: 'before' },
241
+ ],
242
+ pathGroupsExcludedImportTypes: ['react'],
243
+ },
244
+ ],
245
+ 'import-x/consistent-type-specifier-style': ['error', 'prefer-inline'],
246
+ 'import-x/no-duplicates': ['error', { 'prefer-inline': true }],
247
+ 'import-x/no-anonymous-default-export': [
248
+ 'error',
249
+ {
250
+ allowAnonymousClass: false,
251
+ allowAnonymousFunction: true,
252
+ allowArray: true,
253
+ allowArrowFunction: true,
254
+ allowCallExpression: true,
255
+ allowLiteral: false,
256
+ allowObject: true,
257
+ },
258
+ ],
259
+ 'import-x/no-dynamic-require': 'error',
260
+ 'import-x/prefer-default-export': 'off',
261
+ // ─── General rules ───
262
+ 'no-console': ['warn', { allow: ['warn', 'error', 'info'] }],
263
+ 'no-debugger': 'error',
264
+ 'no-alert': 'error',
265
+ 'prefer-const': 'error',
266
+ 'no-var': 'error',
267
+ 'object-shorthand': 'error',
268
+ 'prefer-template': 'error',
269
+ 'prefer-destructuring': ['error', { object: true, array: false }],
270
+ 'prefer-rest-params': 'error',
271
+ 'prefer-spread': 'error',
272
+ 'prefer-arrow-callback': 'error',
273
+ 'arrow-body-style': 'off',
274
+ 'no-duplicate-imports': ['error', { allowSeparateTypeImports: true }],
275
+ 'no-useless-constructor': 'error',
276
+ 'no-useless-rename': 'error',
277
+ 'no-iterator': 'error',
278
+ 'no-restricted-syntax': [
279
+ 'error',
280
+ {
281
+ selector: 'ForInStatement',
282
+ message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
283
+ },
284
+ {
285
+ selector: 'WithStatement',
286
+ message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
287
+ },
288
+ ],
289
+ },
290
+ },
291
+ ];
@@ -0,0 +1,3 @@
1
+ import type { Linter } from 'eslint';
2
+ export declare const expoConfig: Linter.Config[];
3
+ //# sourceMappingURL=expo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"expo.d.ts","sourceRoot":"","sources":["../../src/eslint/expo.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AA2B7C,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAyBrC,CAAC"}
@@ -0,0 +1,45 @@
1
+ import expoEslint from 'eslint-config-expo/flat.js';
2
+ import reactNativePlugin from 'eslint-plugin-react-native';
3
+ const pluginsToFilter = ['import', 'react-hooks', 'react'];
4
+ const filteredExpoConfig = expoEslint.map((config) => {
5
+ if (!config.plugins) {
6
+ return config;
7
+ }
8
+ const pluginEntries = Object.entries(config.plugins);
9
+ const hasConflict = pluginEntries.some(([key]) => pluginsToFilter.includes(key));
10
+ if (!hasConflict) {
11
+ return config;
12
+ }
13
+ const filteredPlugins = {};
14
+ for (const [key, value] of pluginEntries) {
15
+ if (!pluginsToFilter.includes(key)) {
16
+ filteredPlugins[key] = value;
17
+ }
18
+ }
19
+ return { ...config, plugins: filteredPlugins };
20
+ });
21
+ export const expoConfig = [
22
+ {
23
+ ignores: [
24
+ '**/.expo/**',
25
+ ],
26
+ },
27
+ ...filteredExpoConfig,
28
+ {
29
+ files: ['**/*.{ts,tsx,js,jsx}'],
30
+ plugins: {
31
+ 'react-native': reactNativePlugin,
32
+ },
33
+ rules: {
34
+ // ─── Expo overrides ───
35
+ 'react/no-unescaped-entities': 'off',
36
+ // ─── React Native rules ───
37
+ 'react-native/no-unused-styles': 'error',
38
+ 'react-native/split-platform-components': 'warn',
39
+ 'react-native/no-inline-styles': 'off',
40
+ 'react-native/no-color-literals': 'off',
41
+ 'react-native/no-raw-text': 'off',
42
+ 'react-native/no-single-element-style-arrays': 'warn',
43
+ },
44
+ },
45
+ ];
@@ -0,0 +1,6 @@
1
+ export { baseConfig } from './base.js';
2
+ export { reactConfig } from './react.js';
3
+ export { nextjsConfig } from './nextjs.js';
4
+ export { expoConfig } from './expo.js';
5
+ export { aiConfig } from './ai.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/eslint/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { baseConfig } from './base.js';
2
+ export { reactConfig } from './react.js';
3
+ export { nextjsConfig } from './nextjs.js';
4
+ export { expoConfig } from './expo.js';
5
+ export { aiConfig } from './ai.js';
@@ -0,0 +1,3 @@
1
+ import type { Linter } from 'eslint';
2
+ export declare const nextjsConfig: Linter.Config[];
3
+ //# sourceMappingURL=nextjs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nextjs.d.ts","sourceRoot":"","sources":["../../src/eslint/nextjs.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE7C,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EA2BvC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import nextPlugin from '@next/eslint-plugin-next';
2
+ import reactRefreshPlugin from 'eslint-plugin-react-refresh';
3
+ export const nextjsConfig = [
4
+ {
5
+ ignores: [
6
+ '**/.next/**',
7
+ '**/.open-next/**',
8
+ '**/next-env.d.ts',
9
+ '**/src/components/ui/**',
10
+ ],
11
+ },
12
+ {
13
+ files: ['**/*.{ts,tsx,js,jsx}'],
14
+ plugins: {
15
+ '@next/next': nextPlugin,
16
+ 'react-refresh': reactRefreshPlugin,
17
+ },
18
+ rules: {
19
+ // ─── Next.js recommended + core-web-vitals ───
20
+ ...nextPlugin.configs.recommended.rules,
21
+ ...nextPlugin.configs['core-web-vitals'].rules,
22
+ // ─── React Refresh ───
23
+ 'react-refresh/only-export-components': [
24
+ 'warn',
25
+ { allowConstantExport: true },
26
+ ],
27
+ },
28
+ },
29
+ ];
@@ -0,0 +1,3 @@
1
+ import type { Linter } from 'eslint';
2
+ export declare const reactConfig: Linter.Config[];
3
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/eslint/react.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAU,MAAM,EAAE,MAAM,QAAQ,CAAC;AAE7C,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAqCtC,CAAC"}
@@ -0,0 +1,39 @@
1
+ import reactPlugin from 'eslint-plugin-react';
2
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
3
+ export const reactConfig = [
4
+ {
5
+ files: ['**/*.{ts,tsx,js,jsx}'],
6
+ plugins: {
7
+ react: reactPlugin,
8
+ 'react-hooks': reactHooksPlugin,
9
+ },
10
+ settings: {
11
+ react: {
12
+ version: 'detect',
13
+ },
14
+ },
15
+ rules: {
16
+ // ─── React rules ───
17
+ 'react/prop-types': 'off',
18
+ 'react/display-name': 'off',
19
+ 'react/react-in-jsx-scope': 'off',
20
+ 'react/jsx-uses-react': 'off',
21
+ 'react/jsx-uses-vars': 'error',
22
+ 'react/jsx-no-duplicate-props': 'error',
23
+ 'react/jsx-no-undef': 'error',
24
+ 'react/jsx-pascal-case': 'error',
25
+ 'react/no-direct-mutation-state': 'error',
26
+ 'react/no-unescaped-entities': 'warn',
27
+ 'react/self-closing-comp': 'error',
28
+ 'react/jsx-boolean-value': ['error', 'never'],
29
+ 'react/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
30
+ 'react/jsx-fragments': ['error', 'syntax'],
31
+ 'react/jsx-no-useless-fragment': 'error',
32
+ 'react/jsx-key': ['error', { checkFragmentShorthand: true }],
33
+ 'react/no-array-index-key': 'warn',
34
+ // ─── React Hooks rules ───
35
+ 'react-hooks/rules-of-hooks': 'error',
36
+ 'react-hooks/exhaustive-deps': 'error',
37
+ },
38
+ },
39
+ ];
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "@docyrus/rules",
3
+ "version": "0.0.1",
4
+ "description": "Shared ESLint and Biome configurations for Docyrus projects",
5
+ "type": "module",
6
+ "exports": {
7
+ "./eslint": {
8
+ "types": "./dist/eslint/index.d.ts",
9
+ "default": "./dist/eslint/index.js"
10
+ },
11
+ "./eslint/base": {
12
+ "types": "./dist/eslint/base.d.ts",
13
+ "default": "./dist/eslint/base.js"
14
+ },
15
+ "./eslint/react": {
16
+ "types": "./dist/eslint/react.d.ts",
17
+ "default": "./dist/eslint/react.js"
18
+ },
19
+ "./eslint/nextjs": {
20
+ "types": "./dist/eslint/nextjs.d.ts",
21
+ "default": "./dist/eslint/nextjs.js"
22
+ },
23
+ "./eslint/expo": {
24
+ "types": "./dist/eslint/expo.d.ts",
25
+ "default": "./dist/eslint/expo.js"
26
+ },
27
+ "./eslint/ai": {
28
+ "types": "./dist/eslint/ai.d.ts",
29
+ "default": "./dist/eslint/ai.js"
30
+ },
31
+ "./biome/base": "./dist/biome/base.json",
32
+ "./biome/cloudflare": "./dist/biome/cloudflare.json",
33
+ "./biome/react": "./dist/biome/react.json",
34
+ "./biome/nextjs": "./dist/biome/nextjs.json"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsc && cp -r src/biome dist/biome",
41
+ "clean": "rm -rf dist",
42
+ "verify": "node --input-type=module -e \"import { baseConfig, reactConfig, nextjsConfig, expoConfig, aiConfig } from '@docyrus/rules/eslint'; console.log('All exports OK');\"",
43
+ "prepublishOnly": "pnpm run clean && pnpm run build"
44
+ },
45
+ "dependencies": {
46
+ "@next/eslint-plugin-next": "16.1.6",
47
+ "@stylistic/eslint-plugin": "5.7.1",
48
+ "@typescript-eslint/eslint-plugin": "8.54.0",
49
+ "@typescript-eslint/parser": "8.54.0",
50
+ "enhanced-resolve": "5.19.0",
51
+ "eslint-config-expo": "10.0.0",
52
+ "eslint-import-resolver-typescript": "4.4.4",
53
+ "eslint-plugin-import-x": "4.16.1",
54
+ "eslint-plugin-react": "7.37.5",
55
+ "eslint-plugin-react-hooks": "7.0.1",
56
+ "eslint-plugin-react-native": "5.0.0",
57
+ "eslint-plugin-react-refresh": "0.5.0",
58
+ "eslint-plugin-unused-imports": "4.3.0",
59
+ "eslint-plugin-vercel-ai-security": "1.3.3"
60
+ },
61
+ "devDependencies": {
62
+ "eslint": "10.0.0",
63
+ "typescript": "5.9.3"
64
+ },
65
+ "peerDependencies": {
66
+ "eslint": "^10.0.0",
67
+ "typescript": "^5.9.0",
68
+ "@biomejs/biome": "^2.3.0"
69
+ },
70
+ "peerDependenciesMeta": {
71
+ "@biomejs/biome": {
72
+ "optional": true
73
+ }
74
+ },
75
+ "packageManager": "pnpm@10.29.1",
76
+ "engines": {
77
+ "node": ">=24"
78
+ }
79
+ }