eslint-plugin-code-policy 0.2.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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +376 -0
  3. package/dist/chunk-URER6VHN.js +525 -0
  4. package/dist/chunk-URER6VHN.js.map +1 -0
  5. package/dist/chunk-YKNN7DF3.cjs +531 -0
  6. package/dist/chunk-YKNN7DF3.cjs.map +1 -0
  7. package/dist/configs/next.cjs +9 -0
  8. package/dist/configs/next.cjs.map +1 -0
  9. package/dist/configs/next.d.ts +14 -0
  10. package/dist/configs/next.d.ts.map +1 -0
  11. package/dist/configs/next.js +6 -0
  12. package/dist/configs/next.js.map +1 -0
  13. package/dist/configs/react.cjs +9 -0
  14. package/dist/configs/react.cjs.map +1 -0
  15. package/dist/configs/react.d.ts +14 -0
  16. package/dist/configs/react.d.ts.map +1 -0
  17. package/dist/configs/react.js +6 -0
  18. package/dist/configs/react.js.map +1 -0
  19. package/dist/configs/recommended.cjs +9 -0
  20. package/dist/configs/recommended.cjs.map +1 -0
  21. package/dist/configs/recommended.d.ts +14 -0
  22. package/dist/configs/recommended.d.ts.map +1 -0
  23. package/dist/configs/recommended.js +6 -0
  24. package/dist/configs/recommended.js.map +1 -0
  25. package/dist/configs/strict.cjs +9 -0
  26. package/dist/configs/strict.cjs.map +1 -0
  27. package/dist/configs/strict.d.ts +14 -0
  28. package/dist/configs/strict.d.ts.map +1 -0
  29. package/dist/configs/strict.js +6 -0
  30. package/dist/configs/strict.js.map +1 -0
  31. package/dist/index.cjs +18 -0
  32. package/dist/index.cjs.map +1 -0
  33. package/dist/index.d.ts +66 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +6 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/rules/atomic-file.d.ts +4 -0
  38. package/dist/rules/atomic-file.d.ts.map +1 -0
  39. package/dist/rules/no-cross-module-deep-imports.d.ts +31 -0
  40. package/dist/rules/no-cross-module-deep-imports.d.ts.map +1 -0
  41. package/dist/rules/no-inline-types.d.ts +4 -0
  42. package/dist/rules/no-inline-types.d.ts.map +1 -0
  43. package/dist/rules/public-api-imports.d.ts +4 -0
  44. package/dist/rules/public-api-imports.d.ts.map +1 -0
  45. package/dist/rules/view-logic-separation.d.ts +4 -0
  46. package/dist/rules/view-logic-separation.d.ts.map +1 -0
  47. package/package.json +92 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Cristian Deluxe
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,376 @@
1
+ <div align="center">
2
+
3
+ <h1>eslint-plugin-code-policy</h1>
4
+
5
+ <p><strong>Architectural linting for TypeScript · React · Next.js</strong></p>
6
+
7
+ [![npm version](https://img.shields.io/npm/v/eslint-plugin-code-policy?color=2563eb&label=npm&style=flat-square)](https://www.npmjs.com/package/eslint-plugin-code-policy)
8
+ [![License: MIT](https://img.shields.io/badge/License-MIT-22c55e?style=flat-square)](../../LICENSE)
9
+ [![ESLint Flat Config](https://img.shields.io/badge/ESLint-v9%2B%20flat%20config-8b5cf6?style=flat-square)](https://eslint.org/docs/latest/use/configure/configuration-files-new)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.4%2B-3b82f6?style=flat-square)](https://www.typescriptlang.org/)
11
+
12
+ </div>
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ **Requirements**
19
+
20
+ - ESLint `^9.0.0` (flat config)
21
+ - TypeScript `^5.4.0`
22
+ - Node.js `>=20`
23
+
24
+ ```bash
25
+ # npm
26
+ npm install --save-dev eslint-plugin-code-policy
27
+
28
+ # pnpm
29
+ pnpm add --save-dev eslint-plugin-code-policy
30
+
31
+ # yarn
32
+ yarn add --dev eslint-plugin-code-policy
33
+ ```
34
+
35
+ ---
36
+
37
+ ## Usage
38
+
39
+ ### Flat config (`eslint.config.mjs`)
40
+
41
+ ```js
42
+ import codePolicy from 'eslint-plugin-code-policy'
43
+
44
+ export default [codePolicy.configs.recommended]
45
+ ```
46
+
47
+ ### Choosing a preset
48
+
49
+ | Preset | Import path | Best for |
50
+ | ------------- | -------------------------------- | ---------------------- |
51
+ | `recommended` | `codePolicy.configs.recommended` | Any TypeScript project |
52
+ | `strict` | `codePolicy.configs.strict` | Maximum enforcement |
53
+ | `react` | `codePolicy.configs.react` | React (Vite, CRA, …) |
54
+ | `next` | `codePolicy.configs.next` | Next.js App Router |
55
+
56
+ ```js
57
+ // Next.js example
58
+ import codePolicy from 'eslint-plugin-code-policy'
59
+
60
+ export default [codePolicy.configs.next]
61
+ ```
62
+
63
+ ### Manual rule configuration
64
+
65
+ If you prefer to cherry-pick rules:
66
+
67
+ ```js
68
+ import codePolicy from 'eslint-plugin-code-policy'
69
+
70
+ export default [
71
+ {
72
+ plugins: { 'code-policy': codePolicy },
73
+ rules: {
74
+ 'code-policy/atomic-file': 'error',
75
+ 'code-policy/no-inline-types': 'error',
76
+ 'code-policy/public-api-imports': 'error',
77
+ 'code-policy/no-cross-module-deep-imports': 'error',
78
+ 'code-policy/view-logic-separation': 'error',
79
+ },
80
+ },
81
+ ]
82
+ ```
83
+
84
+ ---
85
+
86
+ ## Rules
87
+
88
+ ### `code-policy/atomic-file`
89
+
90
+ > **Enforce exactly one top-level declaration per file.**
91
+
92
+ Each file must export exactly one top-level unit — a function, class, constant, or type. This is the foundation of hyper-modular architecture: if a file has two things, one of them belongs in a new file.
93
+
94
+ **❌ Incorrect**
95
+
96
+ ```ts
97
+ // ❌ src/UserUtils.ts — two exports in one file
98
+ export function formatUserName(user: User) {
99
+ return `${user.firstName} ${user.lastName}`
100
+ }
101
+
102
+ export function getUserAge(user: User) {
103
+ return new Date().getFullYear() - user.birthYear
104
+ }
105
+ ```
106
+
107
+ **✅ Correct**
108
+
109
+ ```ts
110
+ // ✅ src/formatUserName.ts
111
+ export function formatUserName(user: User) {
112
+ return `${user.firstName} ${user.lastName}`
113
+ }
114
+ ```
115
+
116
+ ```ts
117
+ // ✅ src/getUserAge.ts
118
+ export function getUserAge(user: User) {
119
+ return new Date().getFullYear() - user.birthYear
120
+ }
121
+ ```
122
+
123
+ **Exemptions (automatically skipped)**
124
+
125
+ - `*.config.ts` / `*.config.js` / `*.config.mjs`
126
+ - `index.ts` / `index.tsx` / `index.js` (barrel files)
127
+ - `*.d.ts` (ambient declaration files)
128
+ - Next.js special files: `page.tsx`, `layout.tsx`, `route.ts`, etc. — reserved exports like `GET`, `POST`, `metadata` are not counted.
129
+
130
+ ---
131
+
132
+ ### `code-policy/no-inline-types`
133
+
134
+ > **Enforce that type aliases and interfaces live in their own files.**
135
+
136
+ Type declarations that appear alongside implementation code create hidden coupling and violate the single responsibility principle. Every `type` or `interface` must be in a dedicated file, ideally inside a `types/` directory.
137
+
138
+ **❌ Incorrect**
139
+
140
+ ```ts
141
+ // ❌ Mixed type + implementation in one file
142
+ type UserRole = 'admin' | 'member' | 'guest'
143
+
144
+ export function canEdit(role: UserRole) {
145
+ return role === 'admin'
146
+ }
147
+ ```
148
+
149
+ **✅ Correct**
150
+
151
+ ```ts
152
+ // ✅ src/types/UserRole.ts
153
+ export type UserRole = 'admin' | 'member' | 'guest'
154
+ ```
155
+
156
+ ```ts
157
+ // ✅ src/canEdit.ts
158
+ import type { UserRole } from '@/types/UserRole'
159
+
160
+ export function canEdit(role: UserRole) {
161
+ return role === 'admin'
162
+ }
163
+ ```
164
+
165
+ **Exemptions**
166
+
167
+ - Files inside `types/` or `types/**` directories
168
+ - `*.d.ts` files
169
+ - "Pure type files" — files whose entire body consists only of `import` + `type`/`interface` declarations
170
+
171
+ ---
172
+
173
+ ### `code-policy/public-api-imports`
174
+
175
+ > **Prevent importing directly from internal module subpaths.**
176
+
177
+ When consuming a package or module, you must import from its public API (the root / index), not from a deep internal path. Deep imports couple you to internal implementation details.
178
+
179
+ **❌ Incorrect**
180
+
181
+ ```ts
182
+ // ❌ Bypassing the public API
183
+ import { Button } from '@myorg/ui/src/components/Button'
184
+ import { formatDate } from '@myorg/utils/src/date/formatDate'
185
+ ```
186
+
187
+ **✅ Correct**
188
+
189
+ ```ts
190
+ // ✅ Always import through the public surface
191
+ import { Button } from '@myorg/ui'
192
+ import { formatDate } from '@myorg/utils'
193
+ ```
194
+
195
+ **Options**
196
+
197
+ ```js
198
+ {
199
+ 'code-policy/public-api-imports': ['error', {
200
+ bannedSubpaths: ['/src/'] // default
201
+ }]
202
+ }
203
+ ```
204
+
205
+ | Option | Type | Default | Description |
206
+ | ---------------- | ---------- | ----------- | ------------------------------------------- |
207
+ | `bannedSubpaths` | `string[]` | `['/src/']` | Segments that signal a deep internal import |
208
+
209
+ ---
210
+
211
+ ### `code-policy/no-cross-module-deep-imports`
212
+
213
+ > **Prevent relative imports that bypass another module's public API within a monorepo.**
214
+
215
+ In a monorepo, relative paths like `../../core/src/utils/helper` skip the `core` module's public API entirely. This rule detects that pattern by counting `../` traversal depth and checking for internal directory names in the descent.
216
+
217
+ **❌ Incorrect**
218
+
219
+ ```ts
220
+ // ❌ packages/ui/src/Button.tsx
221
+ import { helper } from '../../core/src/utils/helper'
222
+ ```
223
+
224
+ **✅ Correct**
225
+
226
+ ```ts
227
+ // ✅ Import through the published public API
228
+ import { helper } from '@myorg/core'
229
+ ```
230
+
231
+ **Options**
232
+
233
+ ```js
234
+ {
235
+ 'code-policy/no-cross-module-deep-imports': ['error', {
236
+ minParentTraversals: 2, // how many `../` levels before checking
237
+ internalDirs: ['src'] // dirs that signal internal code
238
+ }]
239
+ }
240
+ ```
241
+
242
+ | Option | Type | Default | Description |
243
+ | --------------------- | ---------- | --------- | ------------------------------------------------ |
244
+ | `minParentTraversals` | `number` | `2` | Minimum `../` segments before the rule activates |
245
+ | `internalDirs` | `string[]` | `['src']` | Directory names that indicate internal code |
246
+
247
+ ---
248
+
249
+ ### `code-policy/view-logic-separation`
250
+
251
+ > **Prevent state, effects, and inline handlers inside React view components.**
252
+
253
+ React view components (`.tsx` files) are responsible for rendering only. State management, side effects, and event handler logic must live in a dedicated custom hook. This enforces a clean view/controller split.
254
+
255
+ **❌ Incorrect**
256
+
257
+ ```tsx
258
+ // ❌ src/UserCard.tsx — logic inside a view
259
+ export function UserCard({ userId }: UserCardProps) {
260
+ const [user, setUser] = useState<User | null>(null)
261
+
262
+ useEffect(() => {
263
+ fetchUser(userId).then(setUser)
264
+ }, [userId])
265
+
266
+ const handleDelete = () => {
267
+ deleteUser(userId)
268
+ }
269
+
270
+ return <div onClick={handleDelete}>{user?.name}</div>
271
+ }
272
+ ```
273
+
274
+ **✅ Correct**
275
+
276
+ ```ts
277
+ // ✅ src/useUserCard.ts
278
+ export function useUserCard(userId: string) {
279
+ const [user, setUser] = useState<User | null>(null)
280
+
281
+ useEffect(() => {
282
+ fetchUser(userId).then(setUser)
283
+ }, [userId])
284
+
285
+ const handleDelete = () => deleteUser(userId)
286
+
287
+ return { user, handleDelete }
288
+ }
289
+ ```
290
+
291
+ ```tsx
292
+ // ✅ src/UserCard.tsx — pure view
293
+ import { useUserCard } from './useUserCard'
294
+
295
+ export function UserCard({ userId }: UserCardProps) {
296
+ const { user, handleDelete } = useUserCard(userId)
297
+ return <div onClick={handleDelete}>{user?.name}</div>
298
+ }
299
+ ```
300
+
301
+ **What triggers this rule (inside `.tsx` files)**
302
+
303
+ - Calling React hooks: `useState`, `useEffect`, `useReducer`, `useCallback`, `useMemo`, `useRef`, and more
304
+ - Declaring inline functions/handlers directly inside a view component body
305
+
306
+ ---
307
+
308
+ ## Shareable Configs Reference
309
+
310
+ ### `recommended`
311
+
312
+ Enables all five rules as errors. Best starting point for any TypeScript project.
313
+
314
+ ```js
315
+ // Rules enabled:
316
+ 'code-policy/atomic-file': 'error'
317
+ 'code-policy/no-inline-types': 'error'
318
+ 'code-policy/view-logic-separation': 'error'
319
+ 'code-policy/public-api-imports': 'error'
320
+ 'code-policy/no-cross-module-deep-imports': 'error'
321
+ ```
322
+
323
+ ### `strict`
324
+
325
+ Extends `recommended`. Intended for projects that want zero tolerance for architectural deviation. Reserved for additional strictness overrides in future versions.
326
+
327
+ ### `react`
328
+
329
+ Extends `recommended` with React-specific adjustments.
330
+
331
+ ### `next`
332
+
333
+ Extends `recommended`. Correctly handles Next.js App Router special files (`page.tsx`, `layout.tsx`, `route.ts`, etc.) and reserved exports (`metadata`, `GET`, `POST`, …), preventing false positives.
334
+
335
+ ---
336
+
337
+ ## Migrating from Legacy Config
338
+
339
+ This plugin only supports the **ESLint flat config** format (ESLint v9+). If you're still on the legacy `.eslintrc` format, migrate using the [official ESLint migration guide](https://eslint.org/docs/latest/use/configure/migration-guide) before installing this plugin.
340
+
341
+ ---
342
+
343
+ ## FAQ
344
+
345
+ **Q: Why do I get errors on my `index.ts` barrel files?**
346
+
347
+ `index.ts` files are automatically exempted from the `atomic-file` rule because barrel files by design re-export multiple things.
348
+
349
+ **Q: How do I exempt a specific file from a rule?**
350
+
351
+ Use ESLint's standard inline disable comment:
352
+
353
+ ```ts
354
+ // eslint-disable-next-line code-policy/atomic-file
355
+ ```
356
+
357
+ Or add file overrides in your `eslint.config.mjs`:
358
+
359
+ ```js
360
+ {
361
+ files: ['src/legacy/**'],
362
+ rules: {
363
+ 'code-policy/atomic-file': 'off',
364
+ },
365
+ }
366
+ ```
367
+
368
+ **Q: Does this work with JavaScript (non-TypeScript) projects?**
369
+
370
+ The rules are language-agnostic at the ESLint AST level. TypeScript-specific nodes are handled gracefully. You can use the plugin on `.js` files, though some rules (like `no-inline-types`) are most meaningful in TypeScript codebases.
371
+
372
+ ---
373
+
374
+ ## License
375
+
376
+ [MIT](../../LICENSE)