eslint-plugin-code-style 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,330 +0,0 @@
1
- # Component Rules
2
-
3
- ### `component-props-destructure`
4
-
5
- **What it does:** Enforces that React component props must be destructured in the function parameter, not received as a single `props` object.
6
-
7
- **Why use it:** Destructured props make it immediately clear what props a component uses. It improves readability and helps catch unused props.
8
-
9
- ```typescript
10
- // Good — props are destructured
11
- export const Button = ({ label, onClick, variant = "primary" }) => (
12
- <button onClick={onClick} type="button">
13
- {label}
14
- </button>
15
- );
16
-
17
- export const Card = ({
18
- children,
19
- className = "",
20
- title,
21
- } : {
22
- children: ReactNode,
23
- className?: string,
24
- title: string,
25
- }) => (
26
- <div className={className}>
27
- <h2>{title}</h2>
28
- {children}
29
- </div>
30
- );
31
-
32
- // Bad — props received as single object
33
- export const Button = (props) => (
34
- <button onClick={props.onClick} type="button">
35
- {props.label}
36
- </button>
37
- );
38
-
39
- export const Card = (props: CardPropsInterface) => (
40
- <div className={props.className}>
41
- <h2>{props.title}</h2>
42
- {props.children}
43
- </div>
44
- );
45
- ```
46
-
47
- ---
48
-
49
- ### `component-props-inline-type`
50
-
51
- **What it does:** Enforces that React component props must use inline type annotation instead of referencing an interface or type alias. Also enforces:
52
- - Exactly one space before and after colon: `} : {`
53
- - Props in type must match exactly with destructured props (no missing or extra)
54
- - Each prop type on its own line when there are multiple props
55
- - First prop type must be on new line after `{` when multiple props
56
- - No empty lines after opening brace or before closing brace
57
- - No space before `?` in optional properties (`prop?: type` not `prop ?: type`)
58
- - Trailing commas (not semicolons) for each prop type
59
- - No empty lines between prop types
60
-
61
- **Why use it:** Inline types keep the prop definitions colocated with the component, making it easier to understand and modify the component without jumping to separate interface definitions. Enforcing prop matching ensures type safety and prevents unused type properties.
62
-
63
- ```typescript
64
- // Good — inline type annotation with matching props
65
- export const Button = ({ label } : { label: string }) => (
66
- <button type="button">{label}</button>
67
- );
68
-
69
- export const Card = ({
70
- className = "",
71
- description,
72
- title,
73
- } : {
74
- className?: string,
75
- description?: string,
76
- title: string,
77
- }) => (
78
- <div className={className}>
79
- <h1>{title}</h1>
80
- {description && <p>{description}</p>}
81
- </div>
82
- );
83
-
84
- // Bad — interface reference instead of inline type
85
- interface ButtonPropsInterface {
86
- label: string,
87
- }
88
- export const Button = ({ label }: ButtonPropsInterface) => (
89
- <button type="button">{label}</button>
90
- );
91
-
92
- // Bad — missing space before and after colon
93
- export const Button = ({ label }:{ label: string }) => (
94
- <button type="button">{label}</button>
95
- );
96
-
97
- // Bad — props don't match (extra 'flag' in type, missing in destructured)
98
- export const Card = ({
99
- title,
100
- } : {
101
- flag: boolean,
102
- title: string,
103
- }) => (
104
- <div>{title}</div>
105
- );
106
-
107
- // Bad — semicolons instead of commas
108
- export const Card = ({ title } : { title: string; }) => (
109
- <div>{title}</div>
110
- );
111
-
112
- // Bad — first prop on same line as opening brace
113
- export const Card = ({
114
- title,
115
- } : { title: string,
116
- className?: string,
117
- }) => (
118
- <div>{title}</div>
119
- );
120
-
121
- // Bad — space before ? in optional property
122
- export const Card = ({ title } : { title ?: string }) => (
123
- <div>{title}</div>
124
- );
125
-
126
- // Bad — props on same line when multiple
127
- export const Card = ({ a, b } : { a: string, b: string }) => (
128
- <div>{a}{b}</div>
129
- );
130
- ```
131
-
132
- ---
133
-
134
- ### `folder-based-naming-convention`
135
-
136
- **What it does:** Enforces naming conventions based on folder location, with chained folder names for nested files. Also enforces camelCase suffix for data/constants/strings/services/reducers folders (e.g., `authData`, `apiConstants`, `loginStrings`, `userServices`):
137
-
138
- | Folder | Suffix | Example |
139
- |--------|--------|---------|
140
- | `views/` | View | `DashboardView` |
141
- | `layouts/` | Layout | `MainLayout` |
142
- | `pages/` | Page | `HomePage` |
143
- | `providers/` | Provider | `AuthProvider` |
144
- | `reducers/` | Reducer | `UserReducer` |
145
- | `contexts/` | Context | `AuthContext` |
146
- | `theme/` / `themes/` | Theme | `DarkTheme` |
147
- | `data/` | Data (camelCase) | `authData` |
148
- | `constants/` | Constants (camelCase) | `apiConstants` |
149
- | `strings/` | Strings (camelCase) | `loginStrings` |
150
- | `services/` | Services (camelCase) | `userServices` |
151
- | `atoms/` | *(none)* | `Button` |
152
- | `components/` | *(none)* | `Card` |
153
-
154
- Nested files chain folder names (e.g., `layouts/auth/login.tsx` -> `LoginAuthLayout`, `atoms/input/password.tsx` -> `PasswordInput`).
155
-
156
- **Why use it:** Consistent naming based on folder structure makes purpose immediately clear. The chained naming encodes the full path context into the name. The camelCase suffix for data/constants/strings/services/reducers folders distinguishes these utility modules from PascalCase component-like entities.
157
-
158
- ```tsx
159
- // Good — suffix folders
160
- // in views/dashboard.tsx
161
- export const DashboardView = () => <div>Dashboard</div>;
162
-
163
- // in layouts/auth/login.tsx (chained: Login + Auth + Layout)
164
- export const LoginAuthLayout = () => <div>Login</div>;
165
-
166
- // in providers/auth.tsx
167
- export const AuthProvider = ({ children }) => <AuthContext.Provider>{children}</AuthContext.Provider>;
168
-
169
- // in contexts/auth.ts
170
- export const AuthContext = createContext(null);
171
-
172
- // in reducers/user.ts
173
- export const UserReducer = (state, action) => { ... };
174
-
175
- // in themes/dark.ts
176
- export const DarkTheme = { primary: "#000" };
177
-
178
- // Good — camelCase suffix folders
179
- // in data/auth.ts
180
- export const authData = { ... };
181
-
182
- // in constants/api.ts
183
- export const apiConstants = { ... };
184
-
185
- // in strings/login.ts
186
- export const loginStrings = { ... };
187
-
188
- // in services/user.ts
189
- export const userServices = { ... };
190
-
191
- // Good — no-suffix folders (chaining only)
192
- // in atoms/input/password.tsx (chained: Password + Input)
193
- export const PasswordInput = () => <input type="password" />;
194
-
195
- // in atoms/button.tsx
196
- export const Button = () => <button>Click</button>;
197
-
198
- // in components/card.tsx
199
- export const Card = () => <div>Card</div>;
200
-
201
- // Bad
202
- // in layouts/auth/login.tsx (should be "LoginAuthLayout")
203
- export const Login = () => <div>Login</div>;
204
-
205
- // in reducers/user.ts (should be "UserReducer")
206
- export const User = (state, action) => { ... };
207
-
208
- // in atoms/input/password.tsx (should be "PasswordInput")
209
- export const Password = () => <input type="password" />;
210
- ```
211
-
212
- > **Note:** Module barrel files (e.g., `views/index.ts`) are skipped. Interfaces, enums, and types have their own naming rules (`interface-format`, `enum-format`, `type-format`). Auto-fix renames the identifier and all its references.
213
-
214
- ---
215
-
216
- ### `folder-structure-consistency`
217
-
218
- **What it does:** Enforces that module folders have a consistent internal structure — either all flat files or all wrapped in subfolders. Wrapped mode is only justified when at least one subfolder contains 2+ files. Applies to the same folders as `module-index-exports`: atoms, components, hooks, utils, enums, types, interfaces, reducers, layouts, views, pages, and more.
219
-
220
- **Why use it:** Mixing flat files and wrapped folders in the same directory creates inconsistency. This rule ensures a uniform structure — if one item needs a folder (because it has multiple files), all items should be wrapped; if none do, keep everything flat.
221
-
222
- **Configurable options:**
223
- | Option | Default | Description |
224
- |--------|---------|-------------|
225
- | `moduleFolders` | Same as `module-index-exports` (atoms, components, hooks, utils, enums, types, views, layouts, pages, etc.) | Replace the entire folder list |
226
- | `extraModuleFolders` | `[]` | Add extra folders on top of the defaults |
227
-
228
- ```
229
- // Good — flat mode (all direct files)
230
- atoms/input.tsx
231
- atoms/calendar.tsx
232
-
233
- // Good — wrapped mode (justified — input has multiple files)
234
- atoms/input/index.tsx
235
- atoms/input/helpers.ts
236
- atoms/calendar/index.tsx
237
-
238
- // Bad — mixed (some flat, some wrapped with justification)
239
- atoms/input.tsx -> "all items should be wrapped in folders"
240
- atoms/calendar/index.tsx
241
- atoms/calendar/helpers.ts
242
-
243
- // Bad — wrapped but unnecessary (each folder has only 1 file)
244
- atoms/input/index.tsx -> "use direct files instead"
245
- atoms/calendar/index.tsx
246
-
247
- // Bad — mixed without justification
248
- atoms/input.tsx
249
- atoms/calendar/index.tsx -> "use direct files instead"
250
- ```
251
-
252
- > **Note:** This rule applies equally to all module folders — component folders (atoms, components, views), data folders (enums, types, interfaces), and utility folders (hooks, utils, helpers). The `folder-based-naming-convention` naming rule is separate and enforces export naming based on folder location.
253
-
254
- ---
255
-
256
- ### `no-redundant-folder-suffix`
257
-
258
- **What it does:** Flags files and folders whose name redundantly includes the parent (or ancestor) folder name as a suffix. Since the folder already provides context, the name doesn't need to repeat it.
259
-
260
- **Why use it:** This complements `folder-based-naming-convention` — the *file name* should stay clean while the *exported component name* carries the suffix. For example, `layouts/main.tsx` exporting `MainLayout` is better than `layouts/main-layout.tsx` exporting `MainLayout`.
261
-
262
- ```
263
- // Good — names don't repeat the folder name
264
- layouts/main.tsx -> export const MainLayout = ...
265
- atoms/button.tsx -> file "button" has no redundant suffix
266
- views/dashboard.tsx -> file "dashboard" has no redundant suffix
267
- views/access-control/... -> folder "access-control" has no redundant suffix
268
- atoms/input/index.tsx -> uses "index" inside folder (correct)
269
-
270
- // Bad — file name matches parent folder name (use index instead)
271
- atoms/input/input.tsx -> use "input/index.tsx" instead
272
- components/card/card.tsx -> use "card/index.tsx" instead
273
-
274
- // Bad — file names redundantly include the folder suffix
275
- layouts/main-layout.tsx -> redundant "-layout" (already in layouts/)
276
- atoms/button-atom.tsx -> redundant "-atom" (already in atoms/)
277
- views/dashboard-view.tsx -> redundant "-view" (already in views/)
278
-
279
- // Bad — folder names redundantly include the ancestor suffix
280
- views/access-control-view/ -> redundant "-view" (already in views/)
281
-
282
- // Nested names are also checked against ancestor folders
283
- atoms/forms/input-atom.tsx -> redundant "-atom" from ancestor "atoms/"
284
- ```
285
-
286
- > **Note:** Index files (`index.ts`, `index.js`, etc.) are skipped for file name checks. Folder names are singularized automatically (e.g., `layouts` -> `layout`, `categories` -> `category`, `classes` -> `class`).
287
-
288
- ---
289
-
290
- ### `svg-icon-naming-convention`
291
-
292
- **What it does:** Enforces naming conventions for SVG icon components:
293
- - Components that return only an SVG element must have a name ending with "Icon"
294
- - Components with "Icon" suffix must return an SVG element
295
-
296
- **Why use it:** Consistent naming makes it immediately clear which components render icons, improving code readability and making icon components easier to find in large codebases.
297
-
298
- ```tsx
299
- // Good — returns SVG and ends with "Icon"
300
- export const SuccessIcon = ({ className = "" }: { className?: string }) => (
301
- <svg className={className}>
302
- <path d="M9 12l2 2 4-4" />
303
- </svg>
304
- );
305
-
306
- // Good — returns non-SVG and doesn't end with "Icon"
307
- export const Button = ({ children }: { children: React.ReactNode }) => (
308
- <button>{children}</button>
309
- );
310
-
311
- // Bad — returns SVG but doesn't end with "Icon"
312
- export const Success = ({ className = "" }: { className?: string }) => (
313
- <svg className={className}>
314
- <path d="M9 12l2 2 4-4" />
315
- </svg>
316
- );
317
- // Error: Component "Success" returns an SVG element and should end with "Icon" suffix
318
-
319
- // Bad — ends with "Icon" but doesn't return SVG
320
- export const ButtonIcon = ({ children }: { children: React.ReactNode }) => (
321
- <button>{children}</button>
322
- );
323
- // Error: Component "ButtonIcon" has "Icon" suffix but doesn't return an SVG element
324
- ```
325
-
326
- <br />
327
-
328
- ---
329
-
330
- [<- Back to Rules Index](./README.md) | [<- Back to Main README](../../README.md)