@hatem427/code-guard-ci 3.2.0 → 3.4.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/config/angular.config.ts +29 -708
- package/config/guidelines.config.ts +5 -130
- package/config/nextjs.config.ts +27 -511
- package/config/react.config.ts +19 -614
- package/dist/config/angular.config.d.ts +5 -8
- package/dist/config/angular.config.d.ts.map +1 -1
- package/dist/config/angular.config.js +28 -666
- package/dist/config/angular.config.js.map +1 -1
- package/dist/config/guidelines.config.d.ts.map +1 -1
- package/dist/config/guidelines.config.js +5 -127
- package/dist/config/guidelines.config.js.map +1 -1
- package/dist/config/nextjs.config.d.ts +7 -9
- package/dist/config/nextjs.config.d.ts.map +1 -1
- package/dist/config/nextjs.config.js +26 -472
- package/dist/config/nextjs.config.js.map +1 -1
- package/dist/config/react.config.d.ts +4 -5
- package/dist/config/react.config.d.ts.map +1 -1
- package/dist/config/react.config.js +19 -586
- package/dist/config/react.config.js.map +1 -1
- package/dist/scripts/auto-fix.d.ts +0 -5
- package/dist/scripts/auto-fix.d.ts.map +1 -1
- package/dist/scripts/auto-fix.js +0 -5
- package/dist/scripts/auto-fix.js.map +1 -1
- package/dist/scripts/cli.js +209 -390
- package/dist/scripts/cli.js.map +1 -1
- package/dist/scripts/config-generators/ai-config-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/ai-config-generator.js +71 -15
- package/dist/scripts/config-generators/ai-config-generator.js.map +1 -1
- package/dist/scripts/config-generators/eslint-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/eslint-generator.js +13 -625
- package/dist/scripts/config-generators/eslint-generator.js.map +1 -1
- package/dist/scripts/config-generators/index.d.ts +0 -1
- package/dist/scripts/config-generators/index.d.ts.map +1 -1
- package/dist/scripts/config-generators/index.js +1 -5
- package/dist/scripts/config-generators/index.js.map +1 -1
- package/dist/scripts/config-generators/typescript-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/typescript-generator.js +0 -33
- package/dist/scripts/config-generators/typescript-generator.js.map +1 -1
- package/dist/scripts/config-generators/vscode-generator.d.ts.map +1 -1
- package/dist/scripts/config-generators/vscode-generator.js +28 -171
- package/dist/scripts/config-generators/vscode-generator.js.map +1 -1
- package/dist/scripts/generate-pr-checklist.d.ts +0 -5
- package/dist/scripts/generate-pr-checklist.d.ts.map +1 -1
- package/dist/scripts/generate-pr-checklist.js +1 -6
- package/dist/scripts/generate-pr-checklist.js.map +1 -1
- package/dist/scripts/postinstall.js +0 -38
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/precommit-check.d.ts +0 -5
- package/dist/scripts/precommit-check.d.ts.map +1 -1
- package/dist/scripts/precommit-check.js +137 -121
- package/dist/scripts/precommit-check.js.map +1 -1
- package/dist/scripts/utils/naming-validator.d.ts.map +1 -1
- package/dist/scripts/utils/naming-validator.js +2 -96
- package/dist/scripts/utils/naming-validator.js.map +1 -1
- package/dist/scripts/utils/project-detector.d.ts +9 -12
- package/dist/scripts/utils/project-detector.d.ts.map +1 -1
- package/dist/scripts/utils/project-detector.js +11 -63
- package/dist/scripts/utils/project-detector.js.map +1 -1
- package/dist/scripts/utils/report-generator.js +5 -17
- package/dist/scripts/utils/report-generator.js.map +1 -1
- package/dist/scripts/utils/structure-validator.d.ts.map +1 -1
- package/dist/scripts/utils/structure-validator.js +0 -50
- package/dist/scripts/utils/structure-validator.js.map +1 -1
- package/package.json +1 -12
- package/scripts/auto-fix.ts +0 -5
- package/scripts/cli.ts +224 -424
- package/scripts/config-generators/ai-config-generator.ts +78 -28
- package/scripts/config-generators/eslint-generator.ts +7 -621
- package/scripts/config-generators/index.ts +0 -1
- package/scripts/config-generators/typescript-generator.ts +0 -36
- package/scripts/config-generators/vscode-generator.ts +40 -178
- package/scripts/generate-pr-checklist.ts +1 -6
- package/scripts/postinstall.ts +0 -38
- package/scripts/precommit-check.ts +143 -224
- package/scripts/utils/naming-validator.ts +2 -104
- package/scripts/utils/project-detector.ts +11 -78
- package/scripts/utils/report-generator.ts +5 -19
- package/scripts/utils/structure-validator.ts +0 -54
- package/config/fastify.config.ts +0 -326
- package/config/hono.config.ts +0 -331
- package/config/nestjs.config.ts +0 -500
- package/config/python.config.ts +0 -512
- package/templates/feature-doc-api.md +0 -101
- package/templates/feature-doc-backend.md +0 -114
- package/templates/feature-doc-service.md +0 -113
- package/templates/feature-doc-ui.md +0 -91
|
@@ -1,282 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
3
|
* ============================================================================
|
|
4
|
-
* react.config.ts — React-specific coding rules
|
|
4
|
+
* react.config.ts — React-specific coding rules
|
|
5
5
|
* ============================================================================
|
|
6
6
|
*
|
|
7
7
|
* Registers rules that only apply to React projects:
|
|
8
|
-
* -
|
|
9
|
-
* - Server Components patterns
|
|
8
|
+
* - Hook usage patterns
|
|
10
9
|
* - Props/state typing
|
|
11
|
-
* -
|
|
12
|
-
* -
|
|
10
|
+
* - Component patterns
|
|
11
|
+
* - Performance anti-patterns
|
|
13
12
|
*/
|
|
14
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
14
|
exports.reactRules = void 0;
|
|
16
15
|
const guidelines_config_1 = require("./guidelines.config");
|
|
17
16
|
const reactRules = [
|
|
18
|
-
// ── NEW: React 19+ Features ────────────────────────────────────────────
|
|
19
|
-
// ─────────────────────────────────────────
|
|
20
|
-
// RULE: react-use-hook
|
|
21
|
-
// ROLE: Recommend React 19 use() hook for promise/context unwrapping
|
|
22
|
-
// PURPOSE: use() replaces .then() chains and useContext() with a
|
|
23
|
-
// single API that works in both Server and Client Components.
|
|
24
|
-
// It suspends the component until the promise resolves.
|
|
25
|
-
// EXAMPLE:
|
|
26
|
-
// WRONG:
|
|
27
|
-
// const data = await fetchData().then(res => res.json());
|
|
28
|
-
// const theme = useContext(ThemeContext);
|
|
29
|
-
// RIGHT:
|
|
30
|
-
// import { use } from 'react';
|
|
31
|
-
// const data = use(fetchData());
|
|
32
|
-
// const theme = use(ThemeContext);
|
|
33
|
-
// ─────────────────────────────────────────
|
|
34
|
-
{
|
|
35
|
-
id: 'react-use-hook',
|
|
36
|
-
label: 'Use use() hook for unwrapping promises and context',
|
|
37
|
-
description: 'React 19 introduces use() hook to unwrap promises directly in components and access context in Server Components. Example: const data = use(fetchData())',
|
|
38
|
-
severity: 'warning',
|
|
39
|
-
fileExtensions: ['tsx', 'jsx'],
|
|
40
|
-
pattern: null,
|
|
41
|
-
customCheck: (file) => {
|
|
42
|
-
const violations = [];
|
|
43
|
-
if (/\.then\s*\(|async\s*\(\s*\).*=>.*=>/.test(file.content) &&
|
|
44
|
-
!file.content.includes('use(')) {
|
|
45
|
-
violations.push({
|
|
46
|
-
line: null,
|
|
47
|
-
message: 'Found promise handling. Use React 19 use() hook to unwrap promises directly.',
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
return violations;
|
|
51
|
-
},
|
|
52
|
-
applicableTo: ['react', 'nextjs'],
|
|
53
|
-
category: 'React 19',
|
|
54
|
-
},
|
|
55
|
-
// ─────────────────────────────────────────
|
|
56
|
-
// RULE: react-use-action-state
|
|
57
|
-
// ROLE: Enforce React 19 form handling pattern
|
|
58
|
-
// PURPOSE: useActionState() eliminates manual pending/error state
|
|
59
|
-
// management for forms. It handles submission state, errors,
|
|
60
|
-
// and progressive enhancement automatically.
|
|
61
|
-
// EXAMPLE:
|
|
62
|
-
// WRONG:
|
|
63
|
-
// const [pending, setPending] = useState(false);
|
|
64
|
-
// const handleSubmit = async (e) => {
|
|
65
|
-
// setPending(true);
|
|
66
|
-
// await submitForm();
|
|
67
|
-
// setPending(false);
|
|
68
|
-
// };
|
|
69
|
-
// RIGHT:
|
|
70
|
-
// const [state, formAction, isPending] = useActionState(submit, initialState);
|
|
71
|
-
// <form action={formAction}>
|
|
72
|
-
// <button disabled={isPending}>{isPending ? 'Loading...' : 'Submit'}</button>
|
|
73
|
-
// </form>
|
|
74
|
-
// ─────────────────────────────────────────
|
|
75
|
-
{
|
|
76
|
-
id: 'react-use-action-state',
|
|
77
|
-
label: 'Use useActionState() for form handling',
|
|
78
|
-
description: 'React 19 useActionState() replaces form submission logic. It handles pending states, actions, and errors automatically.',
|
|
79
|
-
severity: 'warning',
|
|
80
|
-
fileExtensions: ['tsx', 'jsx'],
|
|
81
|
-
pattern: null,
|
|
82
|
-
customCheck: (file) => {
|
|
83
|
-
const violations = [];
|
|
84
|
-
if (/use\(["']use client['"], \{.*\}|<form\s+onSubmit|useState.*pending|useState.*loading/.test(file.content) &&
|
|
85
|
-
!file.content.includes('useActionState') &&
|
|
86
|
-
file.content.includes("'use client'")) {
|
|
87
|
-
violations.push({
|
|
88
|
-
line: null,
|
|
89
|
-
message: 'Form with manual state management detected. Use useActionState() for simpler form handling in React 19.',
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
return violations;
|
|
93
|
-
},
|
|
94
|
-
applicableTo: ['react', 'nextjs'],
|
|
95
|
-
category: 'React 19',
|
|
96
|
-
},
|
|
97
|
-
// ─────────────────────────────────────────
|
|
98
|
-
// RULE: react-use-transition
|
|
99
|
-
// ROLE: Recommend non-blocking UI updates for expensive operations
|
|
100
|
-
// PURPOSE: useTransition() marks state updates as low-priority so the
|
|
101
|
-
// UI stays responsive during expensive re-renders (filtering,
|
|
102
|
-
// sorting, searching large datasets).
|
|
103
|
-
// EXAMPLE:
|
|
104
|
-
// WRONG:
|
|
105
|
-
// const [query, setQuery] = useState('');
|
|
106
|
-
// const handleChange = (e) => setQuery(e.target.value);
|
|
107
|
-
// // UI freezes during expensive filtering
|
|
108
|
-
// RIGHT:
|
|
109
|
-
// const [isPending, startTransition] = useTransition();
|
|
110
|
-
// const handleChange = (e) => {
|
|
111
|
-
// startTransition(() => setQuery(e.target.value));
|
|
112
|
-
// };
|
|
113
|
-
// // UI stays responsive, shows pending state
|
|
114
|
-
// ─────────────────────────────────────────
|
|
115
|
-
{
|
|
116
|
-
id: 'react-use-transition',
|
|
117
|
-
label: 'Use useTransition() for non-blocking updates',
|
|
118
|
-
description: 'Use useTransition() to mark updates as non-blocking (lower priority). Good for search boxes, filters, and heavy computations.',
|
|
119
|
-
severity: 'info',
|
|
120
|
-
fileExtensions: ['tsx', 'jsx'],
|
|
121
|
-
pattern: null,
|
|
122
|
-
customCheck: (file) => {
|
|
123
|
-
const violations = [];
|
|
124
|
-
if (/filter|search|sort|debounce/.test(file.content) &&
|
|
125
|
-
/useState\s*\(/.test(file.content) &&
|
|
126
|
-
!file.content.includes('useTransition')) {
|
|
127
|
-
violations.push({
|
|
128
|
-
line: null,
|
|
129
|
-
message: 'Found filter/search logic. Use useTransition() to keep UI responsive during expensive updates.',
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
return violations;
|
|
133
|
-
},
|
|
134
|
-
applicableTo: ['react', 'nextjs'],
|
|
135
|
-
category: 'React 19',
|
|
136
|
-
},
|
|
137
|
-
// ─────────────────────────────────────────
|
|
138
|
-
// RULE: react-use-optimistic
|
|
139
|
-
// ROLE: Recommend optimistic UI updates for async actions
|
|
140
|
-
// PURPOSE: useOptimistic() shows instant feedback to the user while
|
|
141
|
-
// the server processes the request. If the server fails, the
|
|
142
|
-
// state automatically rolls back.
|
|
143
|
-
// EXAMPLE:
|
|
144
|
-
// WRONG:
|
|
145
|
-
// const handleLike = async () => {
|
|
146
|
-
// await api.like(postId); // User waits...
|
|
147
|
-
// refetch();
|
|
148
|
-
// };
|
|
149
|
-
// RIGHT:
|
|
150
|
-
// const [optimisticLikes, addOptimistic] = useOptimistic(
|
|
151
|
-
// likes,
|
|
152
|
-
// (state, newLike) => [...state, newLike]
|
|
153
|
-
// );
|
|
154
|
-
// const handleLike = async () => {
|
|
155
|
-
// addOptimistic(newLike); // Instant feedback
|
|
156
|
-
// await api.like(postId);
|
|
157
|
-
// };
|
|
158
|
-
// ─────────────────────────────────────────
|
|
159
|
-
{
|
|
160
|
-
id: 'react-use-optimistic',
|
|
161
|
-
label: 'Use useOptimistic() for optimistic updates',
|
|
162
|
-
description: 'React 19 useOptimistic() enables optimistic UI updates before server confirmation. Great for forms and instant feedback.',
|
|
163
|
-
severity: 'info',
|
|
164
|
-
fileExtensions: ['tsx', 'jsx'],
|
|
165
|
-
pattern: null,
|
|
166
|
-
customCheck: (file) => {
|
|
167
|
-
const violations = [];
|
|
168
|
-
const hasAsyncHandler = /const\s+handle\w+\s*=\s*async\s*\(|async\s+function\s+handle/i.test(file.content);
|
|
169
|
-
const hasOptimistic = /useOptimistic/i.test(file.content);
|
|
170
|
-
if (hasAsyncHandler && !hasOptimistic) {
|
|
171
|
-
for (let i = 0; i < file.lines.length; i++) {
|
|
172
|
-
if (/const\s+handle\w+\s*=\s*async|async\s+function\s+handle/i.test(file.lines[i])) {
|
|
173
|
-
violations.push({
|
|
174
|
-
line: i + 1,
|
|
175
|
-
message: `Async form handler found. Use useOptimistic() for instant UI feedback.`,
|
|
176
|
-
});
|
|
177
|
-
break;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return violations;
|
|
182
|
-
},
|
|
183
|
-
applicableTo: ['react', 'nextjs'],
|
|
184
|
-
category: 'React 19',
|
|
185
|
-
},
|
|
186
|
-
// ── Server Components (Next.js 13+, React Canary) ────────────────────
|
|
187
|
-
// ─────────────────────────────────────────
|
|
188
|
-
// RULE: react-server-functions
|
|
189
|
-
// ROLE: Recommend "use server" directive for secure server logic
|
|
190
|
-
// PURPOSE: Server Functions run entirely on the server, enabling
|
|
191
|
-
// direct database access, secret handling, and mutations
|
|
192
|
-
// without creating API routes.
|
|
193
|
-
// EXAMPLE:
|
|
194
|
-
// WRONG:
|
|
195
|
-
// // pages/api/user.ts (API route approach)
|
|
196
|
-
// export async function POST(req) {
|
|
197
|
-
// const data = await req.json();
|
|
198
|
-
// await db.users.create(data);
|
|
199
|
-
// }
|
|
200
|
-
// RIGHT:
|
|
201
|
-
// // actions/user.ts
|
|
202
|
-
// 'use server';
|
|
203
|
-
// export async function createUser(formData: FormData) {
|
|
204
|
-
// await db.users.create(Object.fromEntries(formData));
|
|
205
|
-
// revalidatePath('/users');
|
|
206
|
-
// }
|
|
207
|
-
// ─────────────────────────────────────────
|
|
208
|
-
{
|
|
209
|
-
id: 'react-server-functions',
|
|
210
|
-
label: 'Use Server Functions with "use server" directive',
|
|
211
|
-
description: 'Define async functions with "use server" to run on the server. Enables secure database access and secret handling without API routes.',
|
|
212
|
-
severity: 'info',
|
|
213
|
-
fileExtensions: ['ts', 'tsx'],
|
|
214
|
-
pattern: null,
|
|
215
|
-
applicableTo: ['nextjs'],
|
|
216
|
-
category: 'React Server Functions',
|
|
217
|
-
},
|
|
218
|
-
// ─────────────────────────────────────────
|
|
219
|
-
// RULE: react-server-client-boundary
|
|
220
|
-
// ROLE: Validate "use client" boundary placement
|
|
221
|
-
// PURPOSE: Unnecessarily marking components as "use client" prevents
|
|
222
|
-
// server-side rendering optimizations and increases the
|
|
223
|
-
// JavaScript bundle sent to the browser.
|
|
224
|
-
// EXAMPLE:
|
|
225
|
-
// WRONG:
|
|
226
|
-
// 'use client'; // No hooks, no interactivity!
|
|
227
|
-
// export default function StaticCard({ title }) {
|
|
228
|
-
// return <div>{title}</div>;
|
|
229
|
-
// }
|
|
230
|
-
// RIGHT:
|
|
231
|
-
// // No directive needed — Server Component by default
|
|
232
|
-
// export default function StaticCard({ title }) {
|
|
233
|
-
// return <div>{title}</div>;
|
|
234
|
-
// }
|
|
235
|
-
// ─────────────────────────────────────────
|
|
236
|
-
{
|
|
237
|
-
id: 'react-server-client-boundary',
|
|
238
|
-
label: 'Verify "use client" boundaries',
|
|
239
|
-
description: 'Interleave Server and Client Components properly. Only mark Client Components with "use client" to minimize JavaScript.',
|
|
240
|
-
severity: 'warning',
|
|
241
|
-
fileExtensions: ['tsx', 'jsx', 'ts'],
|
|
242
|
-
pattern: null,
|
|
243
|
-
customCheck: (file) => {
|
|
244
|
-
const violations = [];
|
|
245
|
-
const hasUseClient = file.content.includes("'use client'") || file.content.includes('"use client"');
|
|
246
|
-
const hasHooks = /use(State|Effect|Context|Reducer|Callback|Memo|Ref|Transition|OptimisticOptimistic)\s*\(/.test(file.content);
|
|
247
|
-
const hasExport = /export\s+(default\s+)?function|export\s+(default\s+)?const/.test(file.content);
|
|
248
|
-
if (hasUseClient && !hasHooks && hasExport) {
|
|
249
|
-
violations.push({
|
|
250
|
-
line: 1,
|
|
251
|
-
message: '"use client" found but no hooks detected. This component might work as a Server Component for better performance.',
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
return violations;
|
|
255
|
-
},
|
|
256
|
-
applicableTo: ['nextjs'],
|
|
257
|
-
category: 'React Server Components',
|
|
258
|
-
},
|
|
259
17
|
// ── Hooks ──────────────────────────────────────────────────────────────
|
|
260
|
-
// ─────────────────────────────────────────
|
|
261
|
-
// RULE: react-no-useeffect-no-deps
|
|
262
|
-
// ROLE: Block useEffect without dependency array
|
|
263
|
-
// PURPOSE: Missing dependency array causes useEffect to run after
|
|
264
|
-
// EVERY render, leading to infinite loops, stale closures,
|
|
265
|
-
// and severe performance issues.
|
|
266
|
-
// EXAMPLE:
|
|
267
|
-
// WRONG:
|
|
268
|
-
// useEffect(() => {
|
|
269
|
-
// fetchData();
|
|
270
|
-
// }); // Runs on every render!
|
|
271
|
-
// RIGHT:
|
|
272
|
-
// useEffect(() => {
|
|
273
|
-
// fetchData();
|
|
274
|
-
// }, []); // Runs once on mount
|
|
275
|
-
// // With dependencies:
|
|
276
|
-
// useEffect(() => {
|
|
277
|
-
// fetchData(id);
|
|
278
|
-
// }, [id]); // Runs when id changes
|
|
279
|
-
// ─────────────────────────────────────────
|
|
280
18
|
{
|
|
281
19
|
id: 'react-no-useeffect-no-deps',
|
|
282
20
|
label: 'useEffect must have dependency array',
|
|
@@ -289,7 +27,9 @@ const reactRules = [
|
|
|
289
27
|
const lines = file.lines;
|
|
290
28
|
for (let i = 0; i < lines.length; i++) {
|
|
291
29
|
const line = lines[i];
|
|
30
|
+
// Detect useEffect( without a closing bracket on subsequent lines containing dependency array
|
|
292
31
|
if (/useEffect\s*\(\s*\(\s*\)\s*=>\s*\{/.test(line)) {
|
|
32
|
+
// Look ahead for the closing of useEffect — find , [] or , [deps])
|
|
293
33
|
let foundDeps = false;
|
|
294
34
|
let parenDepth = 0;
|
|
295
35
|
for (let j = i; j < Math.min(i + 50, lines.length); j++) {
|
|
@@ -299,6 +39,7 @@ const reactRules = [
|
|
|
299
39
|
if (ch === ')')
|
|
300
40
|
parenDepth--;
|
|
301
41
|
}
|
|
42
|
+
// Check if the dependency array is present
|
|
302
43
|
if (/\]\s*\)\s*;?\s*$/.test(lines[j]) || /,\s*\[/.test(lines[j])) {
|
|
303
44
|
foundDeps = true;
|
|
304
45
|
break;
|
|
@@ -319,18 +60,6 @@ const reactRules = [
|
|
|
319
60
|
applicableTo: ['react', 'nextjs'],
|
|
320
61
|
category: 'React Hooks',
|
|
321
62
|
},
|
|
322
|
-
// ─────────────────────────────────────────
|
|
323
|
-
// RULE: react-no-inline-styles
|
|
324
|
-
// ROLE: Block inline style objects in JSX
|
|
325
|
-
// PURPOSE: Inline style={{ }} creates a new object on every render,
|
|
326
|
-
// preventing React's reconciliation from skipping unchanged
|
|
327
|
-
// elements. Use Tailwind or CSS modules instead.
|
|
328
|
-
// EXAMPLE:
|
|
329
|
-
// WRONG:
|
|
330
|
-
// <div style={{ color: 'red', fontSize: '1rem' }}>Hello</div>
|
|
331
|
-
// RIGHT:
|
|
332
|
-
// <div className="text-red-500 text-base">Hello</div>
|
|
333
|
-
// ─────────────────────────────────────────
|
|
334
63
|
{
|
|
335
64
|
id: 'react-no-inline-styles',
|
|
336
65
|
label: 'No inline style objects',
|
|
@@ -341,21 +70,6 @@ const reactRules = [
|
|
|
341
70
|
applicableTo: ['react', 'nextjs'],
|
|
342
71
|
category: 'Styling',
|
|
343
72
|
},
|
|
344
|
-
// ─────────────────────────────────────────
|
|
345
|
-
// RULE: react-no-direct-dom
|
|
346
|
-
// ROLE: Block direct DOM manipulation in React components
|
|
347
|
-
// PURPOSE: React maintains a virtual DOM — direct DOM manipulation
|
|
348
|
-
// bypasses React's reconciliation and causes state
|
|
349
|
-
// inconsistencies, hydration errors, and bugs.
|
|
350
|
-
// EXAMPLE:
|
|
351
|
-
// WRONG:
|
|
352
|
-
// document.getElementById('input').value = 'hello';
|
|
353
|
-
// document.querySelector('.modal').classList.add('open');
|
|
354
|
-
// RIGHT:
|
|
355
|
-
// const inputRef = useRef<HTMLInputElement>(null);
|
|
356
|
-
// const [isOpen, setIsOpen] = useState(false);
|
|
357
|
-
// inputRef.current!.value = 'hello';
|
|
358
|
-
// ─────────────────────────────────────────
|
|
359
73
|
{
|
|
360
74
|
id: 'react-no-direct-dom',
|
|
361
75
|
label: 'No direct DOM manipulation',
|
|
@@ -367,63 +81,16 @@ const reactRules = [
|
|
|
367
81
|
category: 'React Patterns',
|
|
368
82
|
},
|
|
369
83
|
// ── Component patterns ─────────────────────────────────────────────────
|
|
370
|
-
// ─────────────────────────────────────────
|
|
371
|
-
// RULE: react-no-react-fc
|
|
372
|
-
// ROLE: Block deprecated React.FC type
|
|
373
|
-
// PURPOSE: React.FC implicitly includes children, has issues with
|
|
374
|
-
// generics, and doesn't work well with defaultProps. Direct
|
|
375
|
-
// prop typing is cleaner and more explicit.
|
|
376
|
-
// EXAMPLE:
|
|
377
|
-
// WRONG:
|
|
378
|
-
// const UserCard: React.FC<Props> = ({ name }) => { ... };
|
|
379
|
-
// RIGHT:
|
|
380
|
-
// function UserCard({ name }: Props): React.ReactNode { ... }
|
|
381
|
-
// // Or:
|
|
382
|
-
// const UserCard = ({ name }: Props) => { ... };
|
|
383
|
-
// ─────────────────────────────────────────
|
|
384
84
|
{
|
|
385
|
-
id: 'react-
|
|
386
|
-
label: '
|
|
387
|
-
description: '
|
|
388
|
-
severity: '
|
|
85
|
+
id: 'react-prefer-fc-typing',
|
|
86
|
+
label: 'Type component props explicitly',
|
|
87
|
+
description: 'Define a Props interface/type and use it. Avoid React.FC — type props parameter directly.',
|
|
88
|
+
severity: 'info',
|
|
389
89
|
fileExtensions: ['tsx'],
|
|
390
90
|
pattern: /React\.FC/g,
|
|
391
91
|
applicableTo: ['react', 'nextjs'],
|
|
392
92
|
category: 'TypeScript',
|
|
393
93
|
},
|
|
394
|
-
// ─────────────────────────────────────────
|
|
395
|
-
// RULE: react-explicit-return-types
|
|
396
|
-
// ROLE: Recommend explicit return types for component functions
|
|
397
|
-
// PURPOSE: Explicit return types catch accidental non-JSX returns,
|
|
398
|
-
// prevent undefined leaks, and improve code documentation.
|
|
399
|
-
// EXAMPLE:
|
|
400
|
-
// WRONG:
|
|
401
|
-
// export function UserCard(props: Props) { ... }
|
|
402
|
-
// RIGHT:
|
|
403
|
-
// export function UserCard(props: Props): React.ReactNode { ... }
|
|
404
|
-
// ─────────────────────────────────────────
|
|
405
|
-
{
|
|
406
|
-
id: 'react-explicit-return-types',
|
|
407
|
-
label: 'Add explicit return types for components',
|
|
408
|
-
description: 'Export function components with explicit return type: (props: Props): React.ReactNode => { ... } for better type safety.',
|
|
409
|
-
severity: 'info',
|
|
410
|
-
fileExtensions: ['tsx', 'jsx'],
|
|
411
|
-
pattern: /export\s+(?:default\s+)?function\s+\w+\s*\(\s*props/g,
|
|
412
|
-
applicableTo: ['react', 'nextjs'],
|
|
413
|
-
category: 'TypeScript',
|
|
414
|
-
},
|
|
415
|
-
// ─────────────────────────────────────────
|
|
416
|
-
// RULE: react-no-prop-spreading
|
|
417
|
-
// ROLE: Discourage prop spreading for API clarity
|
|
418
|
-
// PURPOSE: Spreading props ({...props}) hides the component API,
|
|
419
|
-
// passes unintended props, and breaks TypeScript strictness.
|
|
420
|
-
// Explicitly passing required props is safer and self-documenting.
|
|
421
|
-
// EXAMPLE:
|
|
422
|
-
// WRONG:
|
|
423
|
-
// <Button {...props} />
|
|
424
|
-
// RIGHT:
|
|
425
|
-
// <Button onClick={props.onClick} disabled={props.disabled} label={props.label} />
|
|
426
|
-
// ─────────────────────────────────────────
|
|
427
94
|
{
|
|
428
95
|
id: 'react-no-prop-spreading',
|
|
429
96
|
label: 'Avoid prop spreading ({...props})',
|
|
@@ -437,20 +104,6 @@ const reactRules = [
|
|
|
437
104
|
// ── Security Rules ─────────────────────────────────────────────────────
|
|
438
105
|
// NOTE: 'react-no-dangerously-set-html' removed — covered by ESLint 'react/no-danger' rule
|
|
439
106
|
// NOTE: 'no-eval' removed — covered by ESLint 'no-eval' rule
|
|
440
|
-
// ─────────────────────────────────────────
|
|
441
|
-
// RULE: no-hardcoded-credentials
|
|
442
|
-
// ROLE: Block hardcoded secrets in source code
|
|
443
|
-
// PURPOSE: Hardcoded API keys, passwords, and tokens get committed to
|
|
444
|
-
// git, leaked in public repos, and exposed in client bundles.
|
|
445
|
-
// Always use environment variables (.env).
|
|
446
|
-
// EXAMPLE:
|
|
447
|
-
// WRONG:
|
|
448
|
-
// const apiKey = 'sk-1234567890abcdef';
|
|
449
|
-
// const password = 'admin123';
|
|
450
|
-
// RIGHT:
|
|
451
|
-
// const apiKey = process.env.API_KEY;
|
|
452
|
-
// const password = process.env.ADMIN_PASSWORD;
|
|
453
|
-
// ─────────────────────────────────────────
|
|
454
107
|
{
|
|
455
108
|
id: 'no-hardcoded-credentials',
|
|
456
109
|
label: 'No hardcoded credentials',
|
|
@@ -485,20 +138,6 @@ const reactRules = [
|
|
|
485
138
|
applicableTo: ['react', 'nextjs', 'angular'],
|
|
486
139
|
category: 'Security',
|
|
487
140
|
},
|
|
488
|
-
// ─────────────────────────────────────────
|
|
489
|
-
// RULE: require-https
|
|
490
|
-
// ROLE: Block insecure HTTP URLs in source code
|
|
491
|
-
// PURPOSE: HTTP transmits data in plain text — passwords, tokens, and
|
|
492
|
-
// user data can be intercepted. HTTPS is mandatory for all
|
|
493
|
-
// production API calls and external resources.
|
|
494
|
-
// EXAMPLE:
|
|
495
|
-
// WRONG:
|
|
496
|
-
// fetch('http://api.example.com/users');
|
|
497
|
-
// RIGHT:
|
|
498
|
-
// fetch('https://api.example.com/users');
|
|
499
|
-
// // localhost is exempt:
|
|
500
|
-
// fetch('http://localhost:3000/api');
|
|
501
|
-
// ─────────────────────────────────────────
|
|
502
141
|
{
|
|
503
142
|
id: 'require-https',
|
|
504
143
|
label: 'Require HTTPS URLs',
|
|
@@ -510,46 +149,20 @@ const reactRules = [
|
|
|
510
149
|
category: 'Security',
|
|
511
150
|
},
|
|
512
151
|
// ── Performance Rules ──────────────────────────────────────────────────
|
|
513
|
-
// ─────────────────────────────────────────
|
|
514
|
-
// RULE: react-no-anonymous-functions-in-render
|
|
515
|
-
// ROLE: Block anonymous functions in JSX event handlers
|
|
516
|
-
// PURPOSE: Arrow functions in props create a new function reference on
|
|
517
|
-
// every render, defeating React.memo and causing child
|
|
518
|
-
// components to re-render unnecessarily.
|
|
519
|
-
// EXAMPLE:
|
|
520
|
-
// WRONG:
|
|
521
|
-
// <Button onClick={() => handleClick(id)} />
|
|
522
|
-
// RIGHT:
|
|
523
|
-
// const handleButtonClick = useCallback(() => handleClick(id), [id]);
|
|
524
|
-
// <Button onClick={handleButtonClick} />
|
|
525
|
-
// ─────────────────────────────────────────
|
|
526
152
|
{
|
|
527
153
|
id: 'react-no-anonymous-functions-in-render',
|
|
528
|
-
label: 'No anonymous functions in JSX
|
|
529
|
-
description: 'Anonymous functions in JSX props cause unnecessary re-renders. Use useCallback
|
|
154
|
+
label: 'No anonymous functions in JSX',
|
|
155
|
+
description: 'Anonymous functions in JSX props cause unnecessary re-renders. Use useCallback or define functions outside render.',
|
|
530
156
|
severity: 'warning',
|
|
531
157
|
fileExtensions: ['tsx', 'jsx'],
|
|
532
|
-
pattern: /onClick\s*=\s*\{\s*\(\s*\)\s
|
|
158
|
+
pattern: /onClick\s*=\s*\{\s*\(\s*\)\s*=>/g,
|
|
533
159
|
applicableTo: ['react', 'nextjs'],
|
|
534
160
|
category: 'Performance',
|
|
535
161
|
},
|
|
536
|
-
// ─────────────────────────────────────────
|
|
537
|
-
// RULE: react-missing-key-prop
|
|
538
|
-
// ROLE: Block list rendering without key props
|
|
539
|
-
// PURPOSE: React uses key to identify which items changed, were added,
|
|
540
|
-
// or removed. Missing keys cause UI bugs and poor performance.
|
|
541
|
-
// Array index as key causes issues with reordering.
|
|
542
|
-
// EXAMPLE:
|
|
543
|
-
// WRONG:
|
|
544
|
-
// items.map(item => <Card title={item.name} />);
|
|
545
|
-
// items.map((item, i) => <Card key={i} title={item.name} />);
|
|
546
|
-
// RIGHT:
|
|
547
|
-
// items.map(item => <Card key={item.id} title={item.name} />);
|
|
548
|
-
// ─────────────────────────────────────────
|
|
549
162
|
{
|
|
550
163
|
id: 'react-missing-key-prop',
|
|
551
164
|
label: 'Missing key prop in list',
|
|
552
|
-
description: 'When rendering lists with .map(), always provide a unique key prop
|
|
165
|
+
description: 'When rendering lists with .map(), always provide a unique key prop for optimal reconciliation.',
|
|
553
166
|
severity: 'error',
|
|
554
167
|
fileExtensions: ['tsx', 'jsx'],
|
|
555
168
|
pattern: null,
|
|
@@ -558,7 +171,9 @@ const reactRules = [
|
|
|
558
171
|
const lines = file.lines;
|
|
559
172
|
for (let i = 0; i < lines.length; i++) {
|
|
560
173
|
const line = lines[i];
|
|
174
|
+
// Detect .map( with JSX but no key prop nearby
|
|
561
175
|
if (/\.\s*map\s*\(/.test(line)) {
|
|
176
|
+
// Look ahead for JSX opening tag
|
|
562
177
|
let foundKey = false;
|
|
563
178
|
for (let j = i; j < Math.min(i + 10, lines.length); j++) {
|
|
564
179
|
if (/key\s*=/.test(lines[j])) {
|
|
@@ -566,6 +181,7 @@ const reactRules = [
|
|
|
566
181
|
break;
|
|
567
182
|
}
|
|
568
183
|
}
|
|
184
|
+
// Check if there's JSX in the map
|
|
569
185
|
let hasJsx = false;
|
|
570
186
|
for (let j = i; j < Math.min(i + 10, lines.length); j++) {
|
|
571
187
|
if (/<[A-Z]/.test(lines[j]) || /<[a-z]/.test(lines[j])) {
|
|
@@ -576,7 +192,7 @@ const reactRules = [
|
|
|
576
192
|
if (hasJsx && !foundKey) {
|
|
577
193
|
violations.push({
|
|
578
194
|
line: i + 1,
|
|
579
|
-
message: 'List items rendered with .map() must have a key prop
|
|
195
|
+
message: 'List items rendered with .map() must have a key prop',
|
|
580
196
|
});
|
|
581
197
|
}
|
|
582
198
|
}
|
|
@@ -586,20 +202,6 @@ const reactRules = [
|
|
|
586
202
|
applicableTo: ['react', 'nextjs'],
|
|
587
203
|
category: 'Performance',
|
|
588
204
|
},
|
|
589
|
-
// ─────────────────────────────────────────
|
|
590
|
-
// RULE: no-large-bundle-imports
|
|
591
|
-
// ROLE: Block large library wildcard imports
|
|
592
|
-
// PURPOSE: Importing entire lodash (~70KB) or moment (~290KB) when
|
|
593
|
-
// you only need one function wastes bandwidth. Use specific
|
|
594
|
-
// imports for tree-shaking support.
|
|
595
|
-
// EXAMPLE:
|
|
596
|
-
// WRONG:
|
|
597
|
-
// import _ from 'lodash';
|
|
598
|
-
// import moment from 'moment';
|
|
599
|
-
// RIGHT:
|
|
600
|
-
// import get from 'lodash/get';
|
|
601
|
-
// import { format } from 'date-fns';
|
|
602
|
-
// ─────────────────────────────────────────
|
|
603
205
|
{
|
|
604
206
|
id: 'no-large-bundle-imports',
|
|
605
207
|
label: 'Avoid large library imports',
|
|
@@ -612,19 +214,6 @@ const reactRules = [
|
|
|
612
214
|
},
|
|
613
215
|
// NOTE: 'require-alt-text' removed — covered by ESLint 'jsx-a11y/alt-text' rule
|
|
614
216
|
// ── Accessibility Rules ────────────────────────────────────────────────
|
|
615
|
-
// ─────────────────────────────────────────
|
|
616
|
-
// RULE: require-button-type
|
|
617
|
-
// ROLE: Enforce type attribute on all button elements
|
|
618
|
-
// PURPOSE: Buttons default to type="submit", which triggers form
|
|
619
|
-
// submission unexpectedly. Always specify type explicitly
|
|
620
|
-
// to prevent accidental form submissions.
|
|
621
|
-
// EXAMPLE:
|
|
622
|
-
// WRONG:
|
|
623
|
-
// <button onClick={handleClick}>Click me</button>
|
|
624
|
-
// RIGHT:
|
|
625
|
-
// <button type="button" onClick={handleClick}>Click me</button>
|
|
626
|
-
// <button type="submit">Submit</button>
|
|
627
|
-
// ─────────────────────────────────────────
|
|
628
217
|
{
|
|
629
218
|
id: 'require-button-type',
|
|
630
219
|
label: 'Buttons must have type attribute',
|
|
@@ -650,21 +239,6 @@ const reactRules = [
|
|
|
650
239
|
category: 'Accessibility',
|
|
651
240
|
},
|
|
652
241
|
// ── Additional Performance Rules ───────────────────────────────────────
|
|
653
|
-
// ─────────────────────────────────────────
|
|
654
|
-
// RULE: react-prefer-lazy-loading
|
|
655
|
-
// ROLE: Recommend code splitting for route components
|
|
656
|
-
// PURPOSE: Eagerly importing all page components increases the initial
|
|
657
|
-
// bundle size. React.lazy() + Suspense splits code by route,
|
|
658
|
-
// loading pages only when navigated to.
|
|
659
|
-
// EXAMPLE:
|
|
660
|
-
// WRONG:
|
|
661
|
-
// import Dashboard from '../pages/Dashboard';
|
|
662
|
-
// import Settings from '../pages/Settings';
|
|
663
|
-
// RIGHT:
|
|
664
|
-
// const Dashboard = lazy(() => import('../pages/Dashboard'));
|
|
665
|
-
// const Settings = lazy(() => import('../pages/Settings'));
|
|
666
|
-
// <Suspense fallback={<Spinner />}><Dashboard /></Suspense>
|
|
667
|
-
// ─────────────────────────────────────────
|
|
668
242
|
{
|
|
669
243
|
id: 'react-prefer-lazy-loading',
|
|
670
244
|
label: 'Consider lazy loading for routes',
|
|
@@ -675,22 +249,6 @@ const reactRules = [
|
|
|
675
249
|
applicableTo: ['react', 'nextjs'],
|
|
676
250
|
category: 'Performance',
|
|
677
251
|
},
|
|
678
|
-
// ─────────────────────────────────────────
|
|
679
|
-
// RULE: react-memo-expensive-components
|
|
680
|
-
// ROLE: Recommend React.memo for large render trees
|
|
681
|
-
// PURPOSE: Components with expensive render logic (>500 chars of JSX)
|
|
682
|
-
// should be wrapped in React.memo to skip re-renders when
|
|
683
|
-
// props haven't changed.
|
|
684
|
-
// EXAMPLE:
|
|
685
|
-
// WRONG:
|
|
686
|
-
// export function DataTable({ rows }) {
|
|
687
|
-
// // ... 500+ lines of complex rendering
|
|
688
|
-
// }
|
|
689
|
-
// RIGHT:
|
|
690
|
-
// export const DataTable = memo(function DataTable({ rows }) {
|
|
691
|
-
// // ... 500+ lines of complex rendering
|
|
692
|
-
// });
|
|
693
|
-
// ─────────────────────────────────────────
|
|
694
252
|
{
|
|
695
253
|
id: 'react-memo-expensive-components',
|
|
696
254
|
label: 'Consider React.memo for expensive renders',
|
|
@@ -701,21 +259,6 @@ const reactRules = [
|
|
|
701
259
|
applicableTo: ['react', 'nextjs'],
|
|
702
260
|
category: 'Performance',
|
|
703
261
|
},
|
|
704
|
-
// ─────────────────────────────────────────
|
|
705
|
-
// RULE: no-object-array-in-deps
|
|
706
|
-
// ROLE: Block unstable references in hook dependency arrays
|
|
707
|
-
// PURPOSE: Objects and arrays create new references on every render.
|
|
708
|
-
// Putting them in dependency arrays triggers hooks on every
|
|
709
|
-
// render, defeating the purpose of the dependency array.
|
|
710
|
-
// EXAMPLE:
|
|
711
|
-
// WRONG:
|
|
712
|
-
// useEffect(() => { ... }, [{ id: 1 }]);
|
|
713
|
-
// useMemo(() => { ... }, [[1, 2, 3]]);
|
|
714
|
-
// RIGHT:
|
|
715
|
-
// useEffect(() => { ... }, [id]);
|
|
716
|
-
// const configRef = useRef(config); // stable reference
|
|
717
|
-
// useEffect(() => { ... }, [configRef]);
|
|
718
|
-
// ─────────────────────────────────────────
|
|
719
262
|
{
|
|
720
263
|
id: 'no-object-array-in-deps',
|
|
721
264
|
label: 'Avoid objects/arrays in dependency arrays',
|
|
@@ -727,18 +270,6 @@ const reactRules = [
|
|
|
727
270
|
category: 'Performance',
|
|
728
271
|
},
|
|
729
272
|
// ── Additional Best Practice Rules ────────────────────────────────────
|
|
730
|
-
// ─────────────────────────────────────────
|
|
731
|
-
// RULE: react-use-fragments
|
|
732
|
-
// ROLE: Recommend React fragments over wrapper divs
|
|
733
|
-
// PURPOSE: Unnecessary wrapper <div>s add DOM nodes, break CSS layouts
|
|
734
|
-
// (flexbox, grid), increase memory usage, and cause
|
|
735
|
-
// accessibility issues with screen readers.
|
|
736
|
-
// EXAMPLE:
|
|
737
|
-
// WRONG:
|
|
738
|
-
// return (<div><Header /><Content /></div>);
|
|
739
|
-
// RIGHT:
|
|
740
|
-
// return (<><Header /><Content /></>);
|
|
741
|
-
// ─────────────────────────────────────────
|
|
742
273
|
{
|
|
743
274
|
id: 'react-use-fragments',
|
|
744
275
|
label: 'Use React fragments instead of divs',
|
|
@@ -749,18 +280,6 @@ const reactRules = [
|
|
|
749
280
|
applicableTo: ['react', 'nextjs'],
|
|
750
281
|
category: 'Best Practices',
|
|
751
282
|
},
|
|
752
|
-
// ─────────────────────────────────────────
|
|
753
|
-
// RULE: prefer-optional-chaining
|
|
754
|
-
// ROLE: Recommend modern null-safe access patterns
|
|
755
|
-
// PURPOSE: Chained && checks are verbose and error-prone. Optional
|
|
756
|
-
// chaining (?.) is built into the language, more readable,
|
|
757
|
-
// and handles null/undefined safely.
|
|
758
|
-
// EXAMPLE:
|
|
759
|
-
// WRONG:
|
|
760
|
-
// user && user.profile && user.profile.name
|
|
761
|
-
// RIGHT:
|
|
762
|
-
// user?.profile?.name
|
|
763
|
-
// ─────────────────────────────────────────
|
|
764
283
|
{
|
|
765
284
|
id: 'prefer-optional-chaining',
|
|
766
285
|
label: 'Use optional chaining',
|
|
@@ -771,18 +290,6 @@ const reactRules = [
|
|
|
771
290
|
applicableTo: [],
|
|
772
291
|
category: 'Modern Syntax',
|
|
773
292
|
},
|
|
774
|
-
// ─────────────────────────────────────────
|
|
775
|
-
// RULE: prefer-nullish-coalescing
|
|
776
|
-
// ROLE: Recommend ?? over || for default values
|
|
777
|
-
// PURPOSE: || treats 0, '', and false as falsy and replaces them.
|
|
778
|
-
// ?? only replaces null/undefined, preserving valid falsy
|
|
779
|
-
// values like 0 (count) and '' (empty string).
|
|
780
|
-
// EXAMPLE:
|
|
781
|
-
// WRONG:
|
|
782
|
-
// const count = data.count || 10; // 0 becomes 10!
|
|
783
|
-
// RIGHT:
|
|
784
|
-
// const count = data.count ?? 10; // 0 stays 0
|
|
785
|
-
// ─────────────────────────────────────────
|
|
786
293
|
{
|
|
787
294
|
id: 'prefer-nullish-coalescing',
|
|
788
295
|
label: 'Use nullish coalescing (??)',
|
|
@@ -793,24 +300,6 @@ const reactRules = [
|
|
|
793
300
|
applicableTo: [],
|
|
794
301
|
category: 'Modern Syntax',
|
|
795
302
|
},
|
|
796
|
-
// ─────────────────────────────────────────
|
|
797
|
-
// RULE: react-error-boundary-usage
|
|
798
|
-
// ROLE: Recommend error boundaries for crash prevention
|
|
799
|
-
// PURPOSE: Without error boundaries, a single thrown error crashes
|
|
800
|
-
// the entire React app. Error boundaries catch render errors
|
|
801
|
-
// and display fallback UI instead.
|
|
802
|
-
// EXAMPLE:
|
|
803
|
-
// WRONG:
|
|
804
|
-
// <App>
|
|
805
|
-
// <BuggyComponent /> {/* Crash kills entire app */}
|
|
806
|
-
// </App>
|
|
807
|
-
// RIGHT:
|
|
808
|
-
// <App>
|
|
809
|
-
// <ErrorBoundary fallback={<ErrorPage />}>
|
|
810
|
-
// <BuggyComponent /> {/* Crash is contained */}
|
|
811
|
-
// </ErrorBoundary>
|
|
812
|
-
// </App>
|
|
813
|
-
// ─────────────────────────────────────────
|
|
814
303
|
{
|
|
815
304
|
id: 'react-error-boundary-usage',
|
|
816
305
|
label: 'Consider error boundaries',
|
|
@@ -821,19 +310,6 @@ const reactRules = [
|
|
|
821
310
|
applicableTo: ['react', 'nextjs'],
|
|
822
311
|
category: 'Error Handling',
|
|
823
312
|
},
|
|
824
|
-
// ─────────────────────────────────────────
|
|
825
|
-
// RULE: no-sync-external-calls
|
|
826
|
-
// ROLE: Block synchronous external API calls
|
|
827
|
-
// PURPOSE: Synchronous fetch/XHR blocks the main thread, freezing
|
|
828
|
-
// the UI completely. Always use async/await for API calls,
|
|
829
|
-
// file operations, and external services.
|
|
830
|
-
// EXAMPLE:
|
|
831
|
-
// WRONG:
|
|
832
|
-
// const data = fetch('/api/data'); // No await!
|
|
833
|
-
// RIGHT:
|
|
834
|
-
// const data = await fetch('/api/data');
|
|
835
|
-
// const json = await data.json();
|
|
836
|
-
// ─────────────────────────────────────────
|
|
837
313
|
{
|
|
838
314
|
id: 'no-sync-external-calls',
|
|
839
315
|
label: 'Avoid synchronous external calls',
|
|
@@ -844,18 +320,6 @@ const reactRules = [
|
|
|
844
320
|
applicableTo: [],
|
|
845
321
|
category: 'Async Patterns',
|
|
846
322
|
},
|
|
847
|
-
// ─────────────────────────────────────────
|
|
848
|
-
// RULE: react-prefer-controlled-inputs
|
|
849
|
-
// ROLE: Recommend controlled components over uncontrolled
|
|
850
|
-
// PURPOSE: Controlled components (value + onChange) keep React as the
|
|
851
|
-
// single source of truth. Uncontrolled (defaultValue + ref)
|
|
852
|
-
// creates two sources of truth and makes validation harder.
|
|
853
|
-
// EXAMPLE:
|
|
854
|
-
// WRONG:
|
|
855
|
-
// <input defaultValue="hello" ref={inputRef} />
|
|
856
|
-
// RIGHT:
|
|
857
|
-
// <input value={name} onChange={(e) => setName(e.target.value)} />
|
|
858
|
-
// ─────────────────────────────────────────
|
|
859
323
|
{
|
|
860
324
|
id: 'react-prefer-controlled-inputs',
|
|
861
325
|
label: 'Prefer controlled components',
|
|
@@ -867,23 +331,6 @@ const reactRules = [
|
|
|
867
331
|
category: 'Forms',
|
|
868
332
|
},
|
|
869
333
|
// NOTE: 'no-index-as-key' removed — covered by ESLint 'react/no-array-index-key' rule
|
|
870
|
-
// ─────────────────────────────────────────
|
|
871
|
-
// RULE: react-cleanup-effects
|
|
872
|
-
// ROLE: Enforce cleanup functions in side-effect hooks
|
|
873
|
-
// PURPOSE: useEffect with subscriptions, timers, or event listeners
|
|
874
|
-
// must return a cleanup function. Without it, listeners
|
|
875
|
-
// accumulate on every re-render, causing memory leaks.
|
|
876
|
-
// EXAMPLE:
|
|
877
|
-
// WRONG:
|
|
878
|
-
// useEffect(() => {
|
|
879
|
-
// window.addEventListener('resize', handleResize);
|
|
880
|
-
// }, []);
|
|
881
|
-
// RIGHT:
|
|
882
|
-
// useEffect(() => {
|
|
883
|
-
// window.addEventListener('resize', handleResize);
|
|
884
|
-
// return () => window.removeEventListener('resize', handleResize);
|
|
885
|
-
// }, []);
|
|
886
|
-
// ─────────────────────────────────────────
|
|
887
334
|
{
|
|
888
335
|
id: 'react-cleanup-effects',
|
|
889
336
|
label: 'Cleanup side effects',
|
|
@@ -894,20 +341,6 @@ const reactRules = [
|
|
|
894
341
|
applicableTo: ['react', 'nextjs'],
|
|
895
342
|
category: 'Memory Management',
|
|
896
343
|
},
|
|
897
|
-
// ─────────────────────────────────────────
|
|
898
|
-
// RULE: prefer-const-assertion
|
|
899
|
-
// ROLE: Recommend const assertions for literal types
|
|
900
|
-
// PURPOSE: "as const" narrows types to literal values and makes
|
|
901
|
-
// objects/arrays readonly, improving type safety and
|
|
902
|
-
// enabling discriminated unions.
|
|
903
|
-
// EXAMPLE:
|
|
904
|
-
// WRONG:
|
|
905
|
-
// const ROLES: string[] = ['admin', 'user', 'viewer'];
|
|
906
|
-
// // Type: string[] — loses literal type info
|
|
907
|
-
// RIGHT:
|
|
908
|
-
// const ROLES = ['admin', 'user', 'viewer'] as const;
|
|
909
|
-
// // Type: readonly ['admin', 'user', 'viewer']
|
|
910
|
-
// ─────────────────────────────────────────
|
|
911
344
|
{
|
|
912
345
|
id: 'prefer-const-assertion',
|
|
913
346
|
label: 'Use const assertions for literal types',
|